Merge branch 'master' into fix-czstring
diff --git a/.github/workflows/update-project-version.yml b/.github/workflows/update-project-version.yml
index 9a00d25..af03a55 100644
--- a/.github/workflows/update-project-version.yml
+++ b/.github/workflows/update-project-version.yml
@@ -22,17 +22,14 @@
         uses: actions/checkout@v4
 
       - name: update project files
-        id: update_files
         run: |
           VER="${{ github.event.inputs.target_version }}"
           SOVER="${{ github.event.inputs.target_soversion }}"
-          echo "bumping version to $VER"
+          echo "bumping to $VER"
 
-          # 1. CMakeLists.txt
+          # 1. cmakelists.txt
           if [ -f CMakeLists.txt ]; then
-            echo "updating cmakelists.txt"
-            # match VERSION only within the project block
-            sed -i "/project[[:space:]]*([[:space:]]*jsoncpp/,/)/ s/VERSION [0-9.]*/VERSION $VER/" CMakeLists.txt
+            sed -i "/project.*jsoncpp/,/)/ s/VERSION [0-9.]*/VERSION $VER/" CMakeLists.txt
             if [ -n "$SOVER" ]; then
               sed -i "s/set(PROJECT_SOVERSION [0-9]*/set(PROJECT_SOVERSION $SOVER/" CMakeLists.txt
             fi
@@ -40,47 +37,74 @@
 
           # 2. meson.build
           if [ -f meson.build ]; then
-            echo "updating meson.build"
-            # match version : 'x.y.z' with flexible quotes and spacing
+            # targeting the version line directly
             sed -i "s/version[[:space:]]*:[[:space:]]*['\"][0-9.]*['\"]/version : '$VER'/" meson.build
             if [ -n "$SOVER" ]; then
-              # matches soversion : '26'
               sed -i "s/soversion[[:space:]]*:[[:space:]]*['\"][0-9]*['\"]/soversion : '$SOVER'/" meson.build
             fi
           fi
 
-          # 3. vcpkg.json
-          if [ -f vcpkg.json ]; then
-            echo "updating vcpkg.json"
-            jq ".version = \"$VER\"" vcpkg.json > vcpkg.json.tmp && mv vcpkg.json.tmp vcpkg.json
+          # 3. module.bazel
+          if [ -f MODULE.bazel ]; then
+            # match only the first 'version' occurrence in the file (the module version)
+            sed -i "0,/version[[:space:]]*=[[:space:]]*['\"][0-9.]*['\"]/s//version = \"$VER\"/" MODULE.bazel
           fi
 
-          # 4. include/json/version.h
+          # 4. vcpkg.json
+          if [ -f vcpkg.json ]; then
+            jq --arg ver "$VER" '.version = $ver' vcpkg.json > tmp.json && mv tmp.json vcpkg.json
+          fi
+
+          # 5. include/json/version.h
           if [ -f include/json/version.h ]; then
-            echo "updating version.h"
-            # 1.9.7.12 -> MAJOR=1, MINOR=9, PATCH=7, QUALIFIER=12
-            IFS='.' read -r MAJOR MINOR PATCH QUALIFIER <<< "$VER"
-            
-            sed -i "s|#define JSONCPP_VERSION_STRING \"[^\"]*\"|#define JSONCPP_VERSION_STRING \"$VER\"|" include/json/version.h
-            sed -i "s|#define JSONCPP_VERSION_MAJOR [0-9]*|#define JSONCPP_VERSION_MAJOR $MAJOR|" include/json/version.h
-            sed -i "s|#define JSONCPP_VERSION_MINOR [0-9]*|#define JSONCPP_VERSION_MINOR $MINOR|" include/json/version.h
-            sed -i "s|#define JSONCPP_VERSION_PATCH [0-9]*|#define JSONCPP_VERSION_PATCH $PATCH|" include/json/version.h
+            MAJOR=$(echo "$VER" | cut -d. -f1)
+            MINOR=$(echo "$VER" | cut -d. -f2)
+            PATCH=$(echo "$VER" | cut -d. -f3)
+            QUALIFIER=$(echo "$VER" | cut -d. -f4 -s)
+
+            sed -i "s/#define JSONCPP_VERSION_STRING \".*\"/#define JSONCPP_VERSION_STRING \"$VER\"/" include/json/version.h
+            sed -i "s/#define JSONCPP_VERSION_MAJOR [0-9]*/#define JSONCPP_VERSION_MAJOR $MAJOR/" include/json/version.h
+            sed -i "s/#define JSONCPP_VERSION_MINOR [0-9]*/#define JSONCPP_VERSION_MINOR $MINOR/" include/json/version.h
+            sed -i "s/#define JSONCPP_VERSION_PATCH [0-9]*/#define JSONCPP_VERSION_PATCH $PATCH/" include/json/version.h
             
             if [ -n "$QUALIFIER" ]; then
-              sed -i "s|#define JSONCPP_VERSION_QUALIFIER.*|#define JSONCPP_VERSION_QUALIFIER $QUALIFIER|" include/json/version.h
+              sed -i "s/#define JSONCPP_VERSION_QUALIFIER.*/#define JSONCPP_VERSION_QUALIFIER $QUALIFIER/" include/json/version.h
             else
-              sed -i "s|#define JSONCPP_VERSION_QUALIFIER.*|#define JSONCPP_VERSION_QUALIFIER|" include/json/version.h
+              sed -i "s/#define JSONCPP_VERSION_QUALIFIER.*/#define JSONCPP_VERSION_QUALIFIER/" include/json/version.h
             fi
           fi
 
+      - name: verify version macros
+        id: verify
+        run: |
+          FILE="include/json/version.h"
+          if [ -f "$FILE" ]; then
+            # extract clean values by stripping everything except digits and dots
+            V_STR=$(grep "JSONCPP_VERSION_STRING" "$FILE" | head -n 1 | cut -d '"' -f 2)
+            V_MAJ=$(grep "JSONCPP_VERSION_MAJOR" "$FILE" | head -n 1 | awk '{print $3}' | tr -cd '0-9')
+            V_MIN=$(grep "JSONCPP_VERSION_MINOR" "$FILE" | head -n 1 | awk '{print $3}' | tr -cd '0-9')
+            V_PAT=$(grep "JSONCPP_VERSION_PATCH" "$FILE" | head -n 1 | awk '{print $3}' | tr -cd '0-9')
+            V_QUA=$(grep "JSONCPP_VERSION_QUALIFIER" "$FILE" | head -n 1 | awk '{print $3}' | tr -cd '0-9')
+
+            # create a unique delimiter for the multi-line output
+            DELIM=$(dd if=/dev/urandom bs=15 count=1 2>/dev/null | base64)
+            echo "report<<$DELIM" >> $GITHUB_OUTPUT
+            echo "| Macro | Value |" >> $GITHUB_OUTPUT
+            echo "| :--- | :--- |" >> $GITHUB_OUTPUT
+            echo "| STRING | \`$V_STR\` |" >> $GITHUB_OUTPUT
+            echo "| MAJOR | \`$V_MAJ\` |" >> $GITHUB_OUTPUT
+            echo "| MINOR | \`$V_MIN\` |" >> $GITHUB_OUTPUT
+            echo "| PATCH | \`$V_PAT\` |" >> $GITHUB_OUTPUT
+            echo "| QUALIFIER | \`${V_QUA:-empty}\` |" >> $GITHUB_OUTPUT
+            echo "$DELIM" >> $GITHUB_OUTPUT
+          fi
+
       - name: sanity check (cmake configure)
         run: |
           if [ -f CMakeLists.txt ]; then
-            mkdir build_check
-            cd build_check
+            mkdir build_check && cd build_check
             cmake .. -DJSONCPP_WITH_TESTS=OFF -DJSONCPP_WITH_POST_BUILD_UNITTEST=OFF
-            cd ..
-            rm -rf build_check
+            cd .. && rm -rf build_check
           fi
 
       - name: create pull request
@@ -94,4 +118,7 @@
             automated version bump.
             - new version: `${{ github.event.inputs.target_version }}`
             - new soversion: `${{ github.event.inputs.target_soversion || 'no change' }}`
+
+            ### header verification
+            ${{ steps.verify.outputs.report }}
           labels: "maintenance"
diff --git a/src/jsontestrunner/CMakeLists.txt b/src/jsontestrunner/CMakeLists.txt
index 1fc71ea..7f536d3 100644
--- a/src/jsontestrunner/CMakeLists.txt
+++ b/src/jsontestrunner/CMakeLists.txt
@@ -48,4 +48,9 @@
         COMMAND "${PYTHON_EXECUTABLE}" -B "${RUNJSONTESTS_PATH}" --with-json-checker  $<TARGET_FILE:jsontestrunner_exe> "${TEST_DIR}/data"
         WORKING_DIRECTORY "${TEST_DIR}/data"
     )
+
+    # Both tests write .actual/.actual-rewrite along with test data, need to prevent collision when running tests via ctest -j
+    set_tests_properties(jsoncpp_readerwriter jsoncpp_readerwriter_json_checker
+        PROPERTIES RESOURCE_LOCK "test_data_files"
+    )
 endif()
diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp
index 185053a..ca49ed5 100644
--- a/src/test_lib_json/main.cpp
+++ b/src/test_lib_json/main.cpp
@@ -3113,6 +3113,17 @@
   checkParse(R"({ 123 : "abc" })");
 }
 
+JSONTEST_FIXTURE_LOCAL(ReaderTest, allowDroppedNullPlaceholders) {
+  Json::Features features;
+  features.allowDroppedNullPlaceholders_ = true;
+  setFeatures(features);
+  checkParse(R"([1,,2])");
+  JSONTEST_ASSERT_EQUAL(3, root.size());
+  JSONTEST_ASSERT_EQUAL(1, root[0].asInt());
+  JSONTEST_ASSERT(root[1].isNull());
+  JSONTEST_ASSERT_EQUAL(2, root[2].asInt());
+}
+
 struct CharReaderTest : JsonTest::TestCase {};
 
 JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithNoErrors) {