Repository: open-source-parsers/jsoncpp (2026-06-16T21:21:03Z)

Issue:
Unexpected behavior with foreach for Json::arrayType
**Describe the bug**
bad foreach cycle for arrayValue

**To Reproduce**
```
	Json::Value j;
	j["version"] = "1.0.0";
	j["name"] = "arch";
	j["arr"] = Json::arrayValue;
	j["arr"][5] = "Hello, World!";
	for (auto it : j["arr"]) {
		std::cout << it << std::endl;
	}
```
in output only "Hello, World"

**Expected behavior**
in output:  5 null's and Hello, World!

**Desktop (please complete the following information):**
 - OS: Win32
 - jsoncpp Version: 1.9.5




Discussion:
What you are asking for would also be unexpected, for other people. In JavaScript, "arrays" are just a map who's keys all happen to be integers. This is mimicking that and is thus arguably correct. Arguably, asking for keys that aren't populated should actually return `undefined` rather than `null`. That said JSON is not actual JavaScript so that argument isn't conclusive. At the same time, I don't think the JSON standard say anything at all about anything past syntax.

What should happen with iteration if other keys were added?:
- `-5`
- `"5"`
- `"foo"`

I agree with this bug, since array keys must be integers and the backing type for json array values in JsonCpp is std::vector. Updating and making a minor version change to accommodate this change in behavior.



Patch:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b2335b580..d8e6d4cfa 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -57,7 +57,7 @@ project(jsoncpp
         # 3. ./CMakeLists.txt
         # 4. ./MODULE.bazel
         # IMPORTANT: also update the PROJECT_SOVERSION!!
-        VERSION 1.9.9 # <major>[.<minor>[.<patch>[.<tweak>]]]
+        VERSION 1.10.0 # <major>[.<minor>[.<patch>[.<tweak>]]]
         LANGUAGES CXX)
 
 message(STATUS "JsonCpp Version: ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
diff --git a/MODULE.bazel b/MODULE.bazel
index ad09c20f7..09607241b 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -9,7 +9,7 @@ module(
     # 3. /CMakeLists.txt
     # 4. /MODULE.bazel
     # IMPORTANT: also update the SOVERSION!!
-    version = "1.9.9",
+    version = "1.10.0",
     compatibility_level = 1,
 )
 
diff --git a/include/json/version.h b/include/json/version.h
index ff435d58d..2068ba011 100644
--- a/include/json/version.h
+++ b/include/json/version.h
@@ -10,10 +10,10 @@
 // 4. /MODULE.bazel
 // IMPORTANT: also update the SOVERSION!!
 
-#define JSONCPP_VERSION_STRING "1.9.9"
+#define JSONCPP_VERSION_STRING "1.10.0"
 #define JSONCPP_VERSION_MAJOR 1
-#define JSONCPP_VERSION_MINOR 9
-#define JSONCPP_VERSION_PATCH 9
+#define JSONCPP_VERSION_MINOR 10
+#define JSONCPP_VERSION_PATCH 0
 #define JSONCPP_VERSION_HEXA                                                   \
   ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) |             \
    (JSONCPP_VERSION_PATCH << 8))
diff --git a/meson.build b/meson.build
index 2cd0b5d33..380b7e2bd 100644
--- a/meson.build
+++ b/meson.build
@@ -10,7 +10,7 @@ project(
   # 3. /CMakeLists.txt
   # 4. /MODULE.bazel
   # IMPORTANT: also update the SOVERSION!!
-  version : '1.9.9',
+  version : '1.10.0',
   default_options : [
     'buildtype=release',
     'cpp_std=c++11',
diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp
index ac881094e..5823bd1d9 100644
--- a/src/lib_json/json_value.cpp
+++ b/src/lib_json/json_value.cpp
@@ -985,6 +985,15 @@ Value& Value::operator[](ArrayIndex index) {
   if (it != value_.map_->end() && (*it).first == key)
     return (*it).second;
 
+  // JSON arrays are dense: materialize any gap between the current size and
+  // `index` with null so that size(), iteration, and serialization stay
+  // consistent. Without this, `arr[5] = x` on an empty array would store a
+  // single element while size() reported 6 and serialization emitted six
+  // (see issue #1611). resize() already grows arrays this same way.
+  for (ArrayIndex i = size(); i < index; ++i)
+    value_.map_->insert(value_.map_->end(),
+                        ObjectValues::value_type(CZString(i), nullSingleton()));
+
   ObjectValues::value_type defaultValue(key, nullSingleton());
   it = value_.map_->insert(it, defaultValue);
   return (*it).second;


Test patch:
diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp
index 9d13fdbe4..6673867dc 100644
--- a/src/test_lib_json/main.cpp
+++ b/src/test_lib_json/main.cpp
@@ -524,6 +524,35 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, resizePopulatesAllMissingElements) {
     JSONTEST_ASSERT_EQUAL(e, Json::Value{});
 }
 
+JSONTEST_FIXTURE_LOCAL(ValueTest, assignBeyondEndPopulatesGapsWithNull) {
+  // Regression test for #1611: assigning past the end of an array via
+  // operator[] must fill the intervening indices with null, so that size(),
+  // iteration, and serialization all agree (JSON arrays are dense). Before the
+  // fix, `arr[5] = x` stored a single element while size() reported 6 and
+  // serialization emitted six, and range-for visited only the one element.
+  Json::Value arr(Json::arrayValue);
+  arr[5] = "Hello, World!";
+
+  JSONTEST_ASSERT_EQUAL(6u, arr.size());
+  JSONTEST_ASSERT_EQUAL(6, std::distance(arr.begin(), arr.end()));
+  for (Json::ArrayIndex i = 0; i < 5; ++i)
+    JSONTEST_ASSERT_EQUAL(Json::Value{}, arr[i]);
+  JSONTEST_ASSERT_EQUAL("Hello, World!", arr[5].asString());
+
+  // Iteration count matches size() and the dense serialization.
+  Json::ArrayIndex iterated = 0;
+  for (const Json::Value& e : arr) {
+    (void)e;
+    ++iterated;
+  }
+  JSONTEST_ASSERT_EQUAL(6u, iterated);
+
+  Json::StreamWriterBuilder b;
+  b.settings_["indentation"] = "";
+  JSONTEST_ASSERT_EQUAL("[null,null,null,null,null,\"Hello, World!\"]",
+                        Json::writeString(b, arr));
+}
+
 JSONTEST_FIXTURE_LOCAL(ValueTest, getArrayValue) {
   Json::Value array;
   for (Json::ArrayIndex i = 0; i < 5; i++)