From bd25fc5ea0e14d19e1451632205c8b99ec0b1c09 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 30 Sep 2024 18:23:00 -0400 Subject: [PATCH 01/56] fix(build): remove `check_required_components` for meson build (#1570) Signed-off-by: Rui Chen --- jsoncppConfig.cmake.meson.in | 2 -- 1 file changed, 2 deletions(-) diff --git a/jsoncppConfig.cmake.meson.in b/jsoncppConfig.cmake.meson.in index 0f4866d6d..be8852d0c 100644 --- a/jsoncppConfig.cmake.meson.in +++ b/jsoncppConfig.cmake.meson.in @@ -4,5 +4,3 @@ @MESON_STATIC_TARGET@ include ( "${CMAKE_CURRENT_LIST_DIR}/jsoncpp-namespaced-targets.cmake" ) - -check_required_components(JsonCpp) From 2b3815c90d163d34bed75dbc657f9545cb3d382f Mon Sep 17 00:00:00 2001 From: Alexandre Detiste Date: Tue, 3 Dec 2024 08:00:25 +0100 Subject: [PATCH 02/56] the cgi module was removed from Python3.13 (#1578) --- devtools/batchbuild.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devtools/batchbuild.py b/devtools/batchbuild.py index 0eb0690e8..bf8be48df 100644 --- a/devtools/batchbuild.py +++ b/devtools/batchbuild.py @@ -9,7 +9,7 @@ import string import subprocess import sys -import cgi +import html class BuildDesc: def __init__(self, prepend_envs=None, variables=None, build_type=None, generator=None): @@ -195,12 +195,12 @@ def generate_html_report(html_report_path, builds): for variable in variables: build_types = sorted(build_types_by_variable[variable]) nb_build_type = len(build_types_by_variable[variable]) - th_vars.append('%s' % (nb_build_type, cgi.escape(' '.join(variable)))) + th_vars.append('%s' % (nb_build_type, html.escape(' '.join(variable)))) for build_type in build_types: - th_build_types.append('%s' % cgi.escape(build_type)) + th_build_types.append('%s' % html.escape(build_type)) tr_builds = [] for generator in sorted(builds_by_generator): - tds = [ '%s\n' % cgi.escape(generator) ] + tds = [ '%s\n' % html.escape(generator) ] for variable in variables: build_types = sorted(build_types_by_variable[variable]) for build_type in build_types: From 3f86349128b044598ce9a19c1ef92f2b7f4131bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=BCtzel?= Date: Tue, 3 Dec 2024 08:19:05 +0100 Subject: [PATCH 03/56] Fix name of static library when targeting MinGW. (#1579) Co-authored-by: Jordan Bayles --- src/lib_json/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib_json/CMakeLists.txt b/src/lib_json/CMakeLists.txt index 152635348..3037eb020 100644 --- a/src/lib_json/CMakeLists.txt +++ b/src/lib_json/CMakeLists.txt @@ -143,7 +143,7 @@ if(BUILD_STATIC_LIBS) # avoid name clashes on windows as the shared import lib is also named jsoncpp.lib if(NOT DEFINED STATIC_SUFFIX AND BUILD_SHARED_LIBS) - if (WIN32) + if (MSVC OR ("${CMAKE_C_SIMULATE_ID}" STREQUAL "MSVC")) set(STATIC_SUFFIX "_static") else() set(STATIC_SUFFIX "") From dca8a24cf8da1fc61b5cf0422cad03474124196c Mon Sep 17 00:00:00 2001 From: Jens Mertelmeyer Date: Thu, 5 Dec 2024 07:28:16 +0100 Subject: [PATCH 04/56] Fix comparison warnings caused by 54fc4e2 (#1575) Co-authored-by: Jordan Bayles --- src/lib_json/json_value.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp index e53643a6d..d9dee50cb 100644 --- a/src/lib_json/json_value.cpp +++ b/src/lib_json/json_value.cpp @@ -684,7 +684,7 @@ Value::UInt Value::asUInt() const { JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range"); return UInt(value_.uint_); case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt), + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0u, maxUInt), "double out of UInt range"); return UInt(value_.real_); case nullValue: @@ -733,7 +733,7 @@ Value::UInt64 Value::asUInt64() const { case uintValue: return UInt64(value_.uint_); case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64), + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0u, maxUInt64), "double out of UInt64 range"); return UInt64(value_.real_); case nullValue: @@ -844,7 +844,7 @@ bool Value::isConvertibleTo(ValueType other) const { type() == booleanValue || type() == nullValue; case uintValue: return isUInt() || - (type() == realValue && InRange(value_.real_, 0, maxUInt)) || + (type() == realValue && InRange(value_.real_, 0u, maxUInt)) || type() == booleanValue || type() == nullValue; case realValue: return isNumeric() || type() == booleanValue || type() == nullValue; From 07a8fe6a235a91e9ad9bd69fae25a3ed07162ac0 Mon Sep 17 00:00:00 2001 From: Billy Donahue Date: Fri, 10 Jan 2025 18:17:00 -0500 Subject: [PATCH 05/56] Drop pre-C++11 alternatives (#1593) * Assume C++11 We already assume C++11 elsewhere, so all pre-11 `#ifdef` branches are dead code at this point. Fixes issue #1591 because we can just use `std::isfinite` etc. assume C++11 in json_reader.cpp as well apply clang-format * valueToString: simplify lookup of special float name --- AUTHORS | 2 +- src/lib_json/json_reader.cpp | 11 ----- src/lib_json/json_writer.cpp | 79 ++++-------------------------------- 3 files changed, 9 insertions(+), 83 deletions(-) diff --git a/AUTHORS b/AUTHORS index e1fa0fc3a..7a3def276 100644 --- a/AUTHORS +++ b/AUTHORS @@ -16,7 +16,7 @@ Baruch Siach Ben Boeckel Benjamin Knecht Bernd Kuhls -Billy Donahue +Billy Donahue Braden McDorman Brandon Myers Brendan Drew diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 10c97aecb..5b6299906 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -23,13 +23,6 @@ #include #include -#if __cplusplus >= 201103L - -#if !defined(sscanf) -#define sscanf std::sscanf -#endif - -#endif //__cplusplus #if defined(_MSC_VER) #if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) @@ -53,11 +46,7 @@ static size_t const stackLimit_g = namespace Json { -#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) using CharReaderPtr = std::unique_ptr; -#else -using CharReaderPtr = std::auto_ptr; -#endif // Implementation of class Features // //////////////////////////////// diff --git a/src/lib_json/json_writer.cpp b/src/lib_json/json_writer.cpp index ee45c43ba..ac14eb11f 100644 --- a/src/lib_json/json_writer.cpp +++ b/src/lib_json/json_writer.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include #include @@ -17,67 +19,6 @@ #include #include -#if __cplusplus >= 201103L -#include -#include - -#if !defined(isnan) -#define isnan std::isnan -#endif - -#if !defined(isfinite) -#define isfinite std::isfinite -#endif - -#else -#include -#include - -#if defined(_MSC_VER) -#if !defined(isnan) -#include -#define isnan _isnan -#endif - -#if !defined(isfinite) -#include -#define isfinite _finite -#endif - -#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) -#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 -#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES - -#endif //_MSC_VER - -#if defined(__sun) && defined(__SVR4) // Solaris -#if !defined(isfinite) -#include -#define isfinite finite -#endif -#endif - -#if defined(__hpux) -#if !defined(isfinite) -#if defined(__ia64) && !defined(finite) -#define isfinite(x) \ - ((sizeof(x) == sizeof(float) ? _Isfinitef(x) : _IsFinite(x))) -#endif -#endif -#endif - -#if !defined(isnan) -// IEEE standard states that NaN values will not compare to themselves -#define isnan(x) ((x) != (x)) -#endif - -#if !defined(__APPLE__) -#if !defined(isfinite) -#define isfinite finite -#endif -#endif -#endif - #if defined(_MSC_VER) // Disable warning about strdup being deprecated. #pragma warning(disable : 4996) @@ -85,11 +26,7 @@ namespace Json { -#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) using StreamWriterPtr = std::unique_ptr; -#else -using StreamWriterPtr = std::auto_ptr; -#endif String valueToString(LargestInt value) { UIntToStringBuffer buffer; @@ -129,12 +66,12 @@ String valueToString(double value, bool useSpecialFloats, // Print into the buffer. We need not request the alternative representation // that always has a decimal point because JSON doesn't distinguish the // concepts of reals and integers. - if (!isfinite(value)) { - static const char* const reps[2][3] = {{"NaN", "-Infinity", "Infinity"}, - {"null", "-1e+9999", "1e+9999"}}; - return reps[useSpecialFloats ? 0 : 1][isnan(value) ? 0 - : (value < 0) ? 1 - : 2]; + if (!std::isfinite(value)) { + if (std::isnan(value)) + return useSpecialFloats ? "NaN" : "null"; + if (value < 0) + return useSpecialFloats ? "-Infinity" : "-1e+9999"; + return useSpecialFloats ? "Infinity" : "1e+9999"; } String buffer(size_t(36), '\0'); From 60ccc1f5deb671e95d2a6cc761f6d03f3c8ade07 Mon Sep 17 00:00:00 2001 From: evalon32 <34560232+evalon32@users.noreply.github.com> Date: Fri, 10 Jan 2025 18:25:25 -0500 Subject: [PATCH 06/56] feat: support std::string_view in Value API (#1584) This adds direct support for `std::string_view` when available (C++17 and above). The current API can be used with `std::string_view` via the low-level two-pointer methods, but is not ergonomic. E.g., compare: ``` Json::Value node; std::string foo, bar, baz; std::string_view foo_sv, bar_sv, baz_sv; // Efficient & readable: node[foo][bar][baz]; // Less efficient, less readable: node[std::string(foo_sv)][std::string(bar_sv)][std::string(baz_sv)]; // Efficient, but a lot less readable: *node.demand(foo_sv.data(), foo_sv.data() + foo_sv.size()) ->demand(bar_sv.data(), bar_sv.data() + bar_sv.size()) ->demand(baz_sv.data(), baz_sv.data() + baz_sv.size()) // After this change, efficient & readable: node[foo_sv][bar_sv][baz_sv]; ``` * The constructor can take a `std::string_view` parameter. The existing overloads taking `const std::string&` and `const char*` are still necessary to support assignment from those types. * `operator[]`, `get()`, `isMember()` and `removeMember()` take a `std::string_view` parameter. This supersedes the overloads taking `const std::string&` and `const char*`. The overloads taking a pair of pointers (begin, end) are preserved for source compatibility. * `getString()` has an overload with a `std::string_view` output parameter. The one with a pair of pointers is preserved for source compatibility. Signed-off-by: Lev Kandel Co-authored-by: Jordan Bayles --- include/json/value.h | 61 +++++++++++++++++++++++++++---- src/lib_json/json_value.cpp | 71 +++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 7 deletions(-) diff --git a/include/json/value.h b/include/json/value.h index 073ed30d9..307493298 100644 --- a/include/json/value.h +++ b/include/json/value.h @@ -39,6 +39,10 @@ #endif #endif +#if __cplusplus >= 201703L +#define JSONCPP_HAS_STRING_VIEW 1 +#endif + #include #include #include @@ -46,6 +50,10 @@ #include #include +#ifdef JSONCPP_HAS_STRING_VIEW +#include +#endif + // Disable warning C4251: : needs to have dll-interface to // be used by... #if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) @@ -342,6 +350,9 @@ class JSON_API Value { */ Value(const StaticString& value); Value(const String& value); +#ifdef JSONCPP_HAS_STRING_VIEW + Value(std::string_view value); +#endif Value(bool value); Value(std::nullptr_t ptr) = delete; Value(const Value& other); @@ -384,6 +395,12 @@ class JSON_API Value { * \return false if !string. (Seg-fault if str or end are NULL.) */ bool getString(char const** begin, char const** end) const; +#ifdef JSONCPP_HAS_STRING_VIEW + /** Get string_view of string-value. + * \return false if !string. (Seg-fault if str is NULL.) + */ + bool getString(std::string_view* str) const; +#endif Int asInt() const; UInt asUInt() const; #if defined(JSON_HAS_INT64) @@ -470,6 +487,15 @@ class JSON_API Value { bool insert(ArrayIndex index, const Value& newValue); bool insert(ArrayIndex index, Value&& newValue); +#ifdef JSONCPP_HAS_STRING_VIEW + /// Access an object value by name, create a null member if it does not exist. + /// \param key may contain embedded nulls. + Value& operator[](std::string_view key); + /// Access an object value by name, returns null if there is no member with + /// that name. + /// \param key may contain embedded nulls. + const Value& operator[](std::string_view key) const; +#else /// Access an object value by name, create a null member if it does not exist. /// \note Because of our implementation, keys are limited to 2^30 -1 chars. /// Exceeding that will cause an exception. @@ -484,6 +510,7 @@ class JSON_API Value { /// that name. /// \param key may contain embedded nulls. const Value& operator[](const String& key) const; +#endif /** \brief Access an object value by name, create a null member if it does not * exist. * @@ -497,18 +524,24 @@ class JSON_API Value { * \endcode */ Value& operator[](const StaticString& key); +#ifdef JSONCPP_HAS_STRING_VIEW /// Return the member named key if it exist, defaultValue otherwise. /// \note deep copy - Value get(const char* key, const Value& defaultValue) const; + Value get(std::string_view key, const Value& defaultValue) const; +#else /// Return the member named key if it exist, defaultValue otherwise. /// \note deep copy - /// \note key may contain embedded nulls. - Value get(const char* begin, const char* end, - const Value& defaultValue) const; + Value get(const char* key, const Value& defaultValue) const; /// Return the member named key if it exist, defaultValue otherwise. /// \note deep copy /// \param key may contain embedded nulls. Value get(const String& key, const Value& defaultValue) const; +#endif + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \note key may contain embedded nulls. + Value get(const char* begin, const char* end, + const Value& defaultValue) const; /// Most general and efficient version of isMember()const, get()const, /// and operator[]const /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 @@ -525,20 +558,28 @@ class JSON_API Value { /// Do nothing if it did not exist. /// \pre type() is objectValue or nullValue /// \post type() is unchanged +#if JSONCPP_HAS_STRING_VIEW + void removeMember(std::string_view key); +#else void removeMember(const char* key); /// Same as removeMember(const char*) /// \param key may contain embedded nulls. void removeMember(const String& key); - /// Same as removeMember(const char* begin, const char* end, Value* removed), - /// but 'key' is null-terminated. - bool removeMember(const char* key, Value* removed); +#endif /** \brief Remove the named map member. * * Update 'removed' iff removed. * \param key may contain embedded nulls. * \return true iff removed (no exceptions) */ +#if JSONCPP_HAS_STRING_VIEW + bool removeMember(std::string_view key, Value* removed); +#else bool removeMember(String const& key, Value* removed); + /// Same as removeMember(const char* begin, const char* end, Value* removed), + /// but 'key' is null-terminated. + bool removeMember(const char* key, Value* removed); +#endif /// Same as removeMember(String const& key, Value* removed) bool removeMember(const char* begin, const char* end, Value* removed); /** \brief Remove the indexed array element. @@ -549,12 +590,18 @@ class JSON_API Value { */ bool removeIndex(ArrayIndex index, Value* removed); +#ifdef JSONCPP_HAS_STRING_VIEW + /// Return true if the object has a member named key. + /// \param key may contain embedded nulls. + bool isMember(std::string_view key) const; +#else /// Return true if the object has a member named key. /// \note 'key' must be null-terminated. bool isMember(const char* key) const; /// Return true if the object has a member named key. /// \param key may contain embedded nulls. bool isMember(const String& key) const; +#endif /// Same as isMember(String const& key)const bool isMember(const char* begin, const char* end) const; diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp index d9dee50cb..527d716f2 100644 --- a/src/lib_json/json_value.cpp +++ b/src/lib_json/json_value.cpp @@ -17,6 +17,10 @@ #include #include +#ifdef JSONCPP_HAS_STRING_VIEW +#include +#endif + // Provide implementation equivalent of std::snprintf for older _MSC compilers #if defined(_MSC_VER) && _MSC_VER < 1900 #include @@ -420,6 +424,14 @@ Value::Value(const String& value) { value.data(), static_cast(value.length())); } +#ifdef JSONCPP_HAS_STRING_VIEW +Value::Value(std::string_view value) { + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue( + value.data(), static_cast(value.length())); +} +#endif + Value::Value(const StaticString& value) { initBasic(stringValue); value_.string_ = const_cast(value.c_str()); @@ -627,6 +639,21 @@ bool Value::getString(char const** begin, char const** end) const { return true; } +#ifdef JSONCPP_HAS_STRING_VIEW +bool Value::getString(std::string_view* str) const { + if (type() != stringValue) + return false; + if (value_.string_ == nullptr) + return false; + const char* begin; + unsigned length; + decodePrefixedString(this->isAllocated(), this->value_.string_, &length, + &begin); + *str = std::string_view(begin, length); + return true; +} +#endif + String Value::asString() const { switch (type()) { case nullValue: @@ -1108,6 +1135,17 @@ Value* Value::demand(char const* begin, char const* end) { "objectValue or nullValue"); return &resolveReference(begin, end); } +#ifdef JSONCPP_HAS_STRING_VIEW +const Value& Value::operator[](std::string_view key) const { + Value const* found = find(key.data(), key.data() + key.length()); + if (!found) + return nullSingleton(); + return *found; +} +Value& Value::operator[](std::string_view key) { + return resolveReference(key.data(), key.data() + key.length()); +} +#else const Value& Value::operator[](const char* key) const { Value const* found = find(key, key + strlen(key)); if (!found) @@ -1128,6 +1166,7 @@ Value& Value::operator[](const char* key) { Value& Value::operator[](const String& key) { return resolveReference(key.data(), key.data() + key.length()); } +#endif Value& Value::operator[](const StaticString& key) { return resolveReference(key.c_str()); @@ -1167,12 +1206,18 @@ Value Value::get(char const* begin, char const* end, Value const* found = find(begin, end); return !found ? defaultValue : *found; } +#ifdef JSONCPP_HAS_STRING_VIEW +Value Value::get(std::string_view key, const Value& defaultValue) const { + return get(key.data(), key.data() + key.length(), defaultValue); +} +#else Value Value::get(char const* key, Value const& defaultValue) const { return get(key, key + strlen(key), defaultValue); } Value Value::get(String const& key, Value const& defaultValue) const { return get(key.data(), key.data() + key.length(), defaultValue); } +#endif bool Value::removeMember(const char* begin, const char* end, Value* removed) { if (type() != objectValue) { @@ -1188,12 +1233,31 @@ bool Value::removeMember(const char* begin, const char* end, Value* removed) { value_.map_->erase(it); return true; } +#ifdef JSONCPP_HAS_STRING_VIEW +bool Value::removeMember(std::string_view key, Value* removed) { + return removeMember(key.data(), key.data() + key.length(), removed); +} +#else bool Value::removeMember(const char* key, Value* removed) { return removeMember(key, key + strlen(key), removed); } bool Value::removeMember(String const& key, Value* removed) { return removeMember(key.data(), key.data() + key.length(), removed); } +#endif + +#ifdef JSONCPP_HAS_STRING_VIEW +void Value::removeMember(std::string_view key) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::removeMember(): requires objectValue"); + if (type() == nullValue) + return; + + CZString actualKey(key.data(), unsigned(key.length()), + CZString::noDuplication); + value_.map_->erase(actualKey); +} +#else void Value::removeMember(const char* key) { JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, "in Json::Value::removeMember(): requires objectValue"); @@ -1204,6 +1268,7 @@ void Value::removeMember(const char* key) { value_.map_->erase(actualKey); } void Value::removeMember(const String& key) { removeMember(key.c_str()); } +#endif bool Value::removeIndex(ArrayIndex index, Value* removed) { if (type() != arrayValue) { @@ -1233,12 +1298,18 @@ bool Value::isMember(char const* begin, char const* end) const { Value const* value = find(begin, end); return nullptr != value; } +#ifdef JSONCPP_HAS_STRING_VIEW +bool Value::isMember(std::string_view key) const { + return isMember(key.data(), key.data() + key.length()); +} +#else bool Value::isMember(char const* key) const { return isMember(key, key + strlen(key)); } bool Value::isMember(String const& key) const { return isMember(key.data(), key.data() + key.length()); } +#endif Value::Members Value::getMemberNames() const { JSON_ASSERT_MESSAGE( From ba004477a6f260dedbe6ef6470b3760192e8d232 Mon Sep 17 00:00:00 2001 From: SwintonStreet Date: Fri, 10 Jan 2025 23:38:47 +0000 Subject: [PATCH 07/56] Added Value::findType with String key (#1574) This adds a convenience function to return a member if it has a specific json type. All isType values are supported. Co-authored-by: Jordan Bayles --- include/json/value.h | 23 ++++++++++ src/lib_json/json_value.cpp | 38 ++++++++++++++++ src/test_lib_json/main.cpp | 86 +++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) diff --git a/include/json/value.h b/include/json/value.h index 307493298..5f6544329 100644 --- a/include/json/value.h +++ b/include/json/value.h @@ -549,6 +549,29 @@ class JSON_API Value { /// Most general and efficient version of isMember()const, get()const, /// and operator[]const Value const* find(const String& key) const; + + /// Calls find and only returns a valid pointer if the type is found + template + Value const* findValue(const String& key) const { + Value const* found = find(key); + if (!found || !(found->*TMemFn)()) + return nullptr; + return found; + } + + Value const* findNull(const String& key) const; + Value const* findBool(const String& key) const; + Value const* findInt(const String& key) const; + Value const* findInt64(const String& key) const; + Value const* findUInt(const String& key) const; + Value const* findUInt64(const String& key) const; + Value const* findIntegral(const String& key) const; + Value const* findDouble(const String& key) const; + Value const* findNumeric(const String& key) const; + Value const* findString(const String& key) const; + Value const* findArray(const String& key) const; + Value const* findObject(const String& key) const; + /// Most general and efficient version of object-mutators. /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp index 527d716f2..a875d28b2 100644 --- a/src/lib_json/json_value.cpp +++ b/src/lib_json/json_value.cpp @@ -1129,6 +1129,44 @@ Value const* Value::find(char const* begin, char const* end) const { Value const* Value::find(const String& key) const { return find(key.data(), key.data() + key.length()); } + +Value const* Value::findNull(const String& key) const { + return findValue(key); +} +Value const* Value::findBool(const String& key) const { + return findValue(key); +} +Value const* Value::findInt(const String& key) const { + return findValue(key); +} +Value const* Value::findInt64(const String& key) const { + return findValue(key); +} +Value const* Value::findUInt(const String& key) const { + return findValue(key); +} +Value const* Value::findUInt64(const String& key) const { + return findValue(key); +} +Value const* Value::findIntegral(const String& key) const { + return findValue(key); +} +Value const* Value::findDouble(const String& key) const { + return findValue(key); +} +Value const* Value::findNumeric(const String& key) const { + return findValue(key); +} +Value const* Value::findString(const String& key) const { + return findValue(key); +} +Value const* Value::findArray(const String& key) const { + return findValue(key); +} +Value const* Value::findObject(const String& key) const { + return findValue(key); +} + Value* Value::demand(char const* begin, char const* end) { JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, "in Json::Value::demand(begin, end): requires " diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index 5a0ce01ce..60f149d5e 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -76,6 +76,8 @@ struct ValueTest : JsonTest::TestCase { Json::Value float_{0.00390625f}; Json::Value array1_; Json::Value object1_; + Json::Value object2_; + Json::Value object3_; Json::Value emptyString_{""}; Json::Value string1_{"a"}; Json::Value string_{"sometext with space"}; @@ -85,6 +87,34 @@ struct ValueTest : JsonTest::TestCase { ValueTest() { array1_.append(1234); object1_["id"] = 1234; + + // object2 with matching values + object2_["null"] = Json::nullValue; + object2_["bool"] = true; + object2_["int"] = Json::Int{Json::Value::maxInt}; + object2_["int64"] = Json::Int64{Json::Value::maxInt64}; + object2_["uint"] = Json::UInt{Json::Value::maxUInt}; + object2_["uint64"] = Json::UInt64{Json::Value::maxUInt64}; + object2_["integral"] = 1234; + object2_["double"] = 1234.56789; + object2_["numeric"] = 0.12345f; + object2_["string"] = "string"; + object2_["array"] = Json::arrayValue; + object2_["object"] = Json::objectValue; + + // object3 with not matching values + object3_["object"] = Json::nullValue; + object3_["null"] = true; + object3_["bool"] = Json::Int{Json::Value::maxInt}; + object3_["int"] = "not_an_int"; + object3_["int64"] = "not_an_int64"; + object3_["uint"] = "not_an_uint"; + object3_["uin64"] = "not_an_uint64"; + object3_["integral"] = 1234.56789; + object3_["double"] = false; + object3_["numeric"] = "string"; + object3_["string"] = Json::arrayValue; + object3_["array"] = Json::objectValue; } struct IsCheck { @@ -234,6 +264,62 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, objects) { const Json::Value* stringFoundUnknownId = object1_.find(stringUnknownIdKey); JSONTEST_ASSERT_EQUAL(nullptr, stringFoundUnknownId); + // Access through find() + const Json::Value* nullFound = object2_.findNull("null"); + JSONTEST_ASSERT(nullFound != nullptr); + JSONTEST_ASSERT_EQUAL(Json::nullValue, *nullFound); + JSONTEST_ASSERT(object3_.findNull("null") == nullptr); + + const Json::Value* boolFound = object2_.findBool("bool"); + JSONTEST_ASSERT(boolFound != nullptr); + JSONTEST_ASSERT_EQUAL(true, *boolFound); + JSONTEST_ASSERT(object3_.findBool("bool") == nullptr); + + const Json::Value* intFound = object2_.findInt("int"); + JSONTEST_ASSERT(intFound != nullptr); + JSONTEST_ASSERT_EQUAL(Json::Int{Json::Value::maxInt}, *intFound); + JSONTEST_ASSERT(object3_.findInt("int") == nullptr); + + const Json::Value* int64Found = object2_.findInt64("int64"); + JSONTEST_ASSERT(int64Found != nullptr); + JSONTEST_ASSERT_EQUAL(Json::Int64{Json::Value::maxInt64}, *int64Found); + JSONTEST_ASSERT(object3_.findInt64("int64") == nullptr); + + const Json::Value* uintFound = object2_.findUInt("uint"); + JSONTEST_ASSERT(uintFound != nullptr); + JSONTEST_ASSERT_EQUAL(Json::UInt{Json::Value::maxUInt}, *uintFound); + JSONTEST_ASSERT(object3_.findUInt("uint") == nullptr); + + const Json::Value* uint64Found = object2_.findUInt64("uint64"); + JSONTEST_ASSERT(uint64Found != nullptr); + JSONTEST_ASSERT_EQUAL(Json::UInt64{Json::Value::maxUInt64}, *uint64Found); + JSONTEST_ASSERT(object3_.findUInt64("uint64") == nullptr); + + const Json::Value* integralFound = object2_.findIntegral("integral"); + JSONTEST_ASSERT(integralFound != nullptr); + JSONTEST_ASSERT_EQUAL(1234, *integralFound); + JSONTEST_ASSERT(object3_.findIntegral("integral") == nullptr); + + const Json::Value* doubleFound = object2_.findDouble("double"); + JSONTEST_ASSERT(doubleFound != nullptr); + JSONTEST_ASSERT_EQUAL(1234.56789, *doubleFound); + JSONTEST_ASSERT(object3_.findDouble("double") == nullptr); + + const Json::Value* numericFound = object2_.findNumeric("numeric"); + JSONTEST_ASSERT(numericFound != nullptr); + JSONTEST_ASSERT_EQUAL(0.12345f, *numericFound); + JSONTEST_ASSERT(object3_.findNumeric("numeric") == nullptr); + + const Json::Value* stringFound = object2_.findString("string"); + JSONTEST_ASSERT(stringFound != nullptr); + JSONTEST_ASSERT_EQUAL(std::string{"string"}, *stringFound); + JSONTEST_ASSERT(object3_.findString("string") == nullptr); + + const Json::Value* arrayFound = object2_.findArray("array"); + JSONTEST_ASSERT(arrayFound != nullptr); + JSONTEST_ASSERT_EQUAL(Json::arrayValue, *arrayFound); + JSONTEST_ASSERT(object3_.findArray("array") == nullptr); + // Access through demand() const char yetAnotherIdKey[] = "yet another id"; const Json::Value* foundYetAnotherId = From 037752d9a1e48c8b7e5a62ee895a352166df03e3 Mon Sep 17 00:00:00 2001 From: bcsgh <33939446+bcsgh@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:57:16 -0700 Subject: [PATCH 08/56] Set up for Bazel module builds. (#1597) * Set up for Bazel module builds. Note: the MODULE.bazel is copied from https://github.com/bazelbuild/bazel-central-registry/blob/main/modules/jsoncpp/1.9.6/MODULE.bazel * More tweaks to .gitignore --- .gitignore | 4 ++++ CMakeLists.txt | 3 ++- MODULE.bazel | 14 ++++++++++++++ include/json/version.h | 3 ++- meson.build | 3 ++- 5 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 MODULE.bazel diff --git a/.gitignore b/.gitignore index 9682782fa..69868f413 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,7 @@ compile_commands.json # temps /version + +# Bazel output paths +/bazel-* +/MODULE.bazel.lock diff --git a/CMakeLists.txt b/CMakeLists.txt index 6104c5ce5..5ab9c52a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,12 +55,13 @@ endif() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") project(jsoncpp - # Note: version must be updated in three places when doing a release. This + # Note: version must be updated in four places when doing a release. This # annoying process ensures that amalgamate, CMake, and meson all report the # correct version. # 1. ./meson.build # 2. ./include/json/version.h # 3. ./CMakeLists.txt + # 4. ./MODULE.bazel # IMPORTANT: also update the PROJECT_SOVERSION!! VERSION 1.9.7 # [.[.[.]]] LANGUAGES CXX) diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 000000000..03f192dd4 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,14 @@ +module( + name = "jsoncpp", + + # Note: version must be updated in four places when doing a release. This + # annoying process ensures that amalgamate, CMake, and meson all report the + # correct version. + # 1. /meson.build + # 2. /include/json/version.h + # 3. /CMakeLists.txt + # 4. /MODULE.bazel + # IMPORTANT: also update the SOVERSION!! + version = "1.9.7", + compatibility_level = 1, +) diff --git a/include/json/version.h b/include/json/version.h index 42e8780a3..555152c8c 100644 --- a/include/json/version.h +++ b/include/json/version.h @@ -1,12 +1,13 @@ #ifndef JSON_VERSION_H_INCLUDED #define JSON_VERSION_H_INCLUDED -// Note: version must be updated in three places when doing a release. This +// Note: version must be updated in four places when doing a release. This // annoying process ensures that amalgamate, CMake, and meson all report the // correct version. // 1. /meson.build // 2. /include/json/version.h // 3. /CMakeLists.txt +// 4. /MODULE.bazel // IMPORTANT: also update the SOVERSION!! #define JSONCPP_VERSION_STRING "1.9.7" diff --git a/meson.build b/meson.build index 8e8d57e3c..2648c3071 100644 --- a/meson.build +++ b/meson.build @@ -2,12 +2,13 @@ project( 'jsoncpp', 'cpp', - # Note: version must be updated in three places when doing a release. This + # Note: version must be updated in four places when doing a release. This # annoying process ensures that amalgamate, CMake, and meson all report the # correct version. # 1. /meson.build # 2. /include/json/version.h # 3. /CMakeLists.txt + # 4. /MODULE.bazel # IMPORTANT: also update the SOVERSION!! version : '1.9.7', default_options : [ From ca98c98457b1163cca1f7d8db62827c115fec6d1 Mon Sep 17 00:00:00 2001 From: bcsgh <33939446+bcsgh@users.noreply.github.com> Date: Tue, 18 Mar 2025 16:15:37 -0700 Subject: [PATCH 09/56] Add a BUILD.bazel file for //example. (#1602) --- example/BUILD.bazel | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 example/BUILD.bazel diff --git a/example/BUILD.bazel b/example/BUILD.bazel new file mode 100644 index 000000000..38e7dfcf7 --- /dev/null +++ b/example/BUILD.bazel @@ -0,0 +1,33 @@ +cc_binary( + name = "readFromStream_ok", + srcs = ["readFromStream/readFromStream.cpp"], + deps = ["//:jsoncpp"], + args = ["$(location :readFromStream/withComment.json)"], + data = ["readFromStream/withComment.json"], +) + +cc_binary( + name = "readFromStream_err", + srcs = ["readFromStream/readFromStream.cpp"], + deps = ["//:jsoncpp"], + args = ["$(location :readFromStream/errorFormat.json)"], + data = ["readFromStream/errorFormat.json"], +) + +cc_binary( + name = "readFromString", + srcs = ["readFromString/readFromString.cpp"], + deps = ["//:jsoncpp"], +) + +cc_binary( + name = "streamWrite", + srcs = ["streamWrite/streamWrite.cpp"], + deps = ["//:jsoncpp"], +) + +cc_binary( + name = "stringWrite", + srcs = ["stringWrite/stringWrite.cpp"], + deps = ["//:jsoncpp"], +) From 9af09c4a4abe5928d1f7a6e7ec1c73a565bb362e Mon Sep 17 00:00:00 2001 From: Victor Vianna Date: Mon, 10 Nov 2025 17:51:20 -0800 Subject: [PATCH 10/56] Fix "include what you use" issue (#1625) Fix IWYU for istreambuf_iterator. Co-authored-by: Victor Hugo Vianna Silva --- src/lib_json/json_reader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 5b6299906..93bb82240 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include From 6f6aca167fb016a47d6bb320e6bcce8e4bfe5708 Mon Sep 17 00:00:00 2001 From: bcsgh <33939446+bcsgh@users.noreply.github.com> Date: Tue, 11 Nov 2025 23:58:32 -0800 Subject: [PATCH 11/56] Make the build configuration under Bazel more correct. (#1600) * Expose JSON_USE_EXCEPTION and JSON_HAS_INT64 as Bazel config flags with defaults that match the existing Bazel build. Switch //:jsoncpp from using copts ro defines for JSON_USE_EXCEPTION and JSON_HAS_INT64 so that rules that depend on it get the same config. Make src/test_lib_json/fuzz.cpp respect JSON_USE_EXCEPTION. * #ifdef stuff that should only be used with JSON_USE_EXCEPTION. --------- Co-authored-by: Jordan Bayles --- BUILD.bazel | 33 +++++++++++++++++++++++++++++---- MODULE.bazel | 5 +++++ src/test_lib_json/fuzz.cpp | 8 ++++---- src/test_lib_json/jsontest.h | 4 ++++ src/test_lib_json/main.cpp | 10 +++++++++- 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/BUILD.bazel b/BUILD.bazel index 6d7ac3da9..2227fee23 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,7 +1,29 @@ licenses(["unencumbered"]) # Public Domain or MIT +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") + exports_files(["LICENSE"]) +bool_flag( + name = "use_exception", + build_setting_default = False, +) + +config_setting( + name = "use_exception_cfg", + flag_values = {":use_exception": "true"}, +) + +bool_flag( + name = "has_int64", + build_setting_default = True, +) + +config_setting( + name = "has_int64_cfg", + flag_values = {":has_int64": "true"}, +) + cc_library( name = "jsoncpp", srcs = [ @@ -22,10 +44,13 @@ cc_library( "include/json/version.h", "include/json/writer.h", ], - copts = [ - "-DJSON_USE_EXCEPTION=0", - "-DJSON_HAS_INT64", - ], + defines = select({ + ":use_exception_cfg": ["JSON_USE_EXCEPTION=1"], + "//conditions:default": ["JSON_USE_EXCEPTION=0"], + }) + select({ + ":has_int64_cfg": ["JSON_HAS_INT64"], + "//conditions:default": [], + }), includes = ["include"], visibility = ["//visibility:public"], deps = [":private"], diff --git a/MODULE.bazel b/MODULE.bazel index 03f192dd4..e60fa06d1 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -12,3 +12,8 @@ module( version = "1.9.7", compatibility_level = 1, ) + +bazel_dep( + name = "bazel_skylib", + version = "1.7.1", +) diff --git a/src/test_lib_json/fuzz.cpp b/src/test_lib_json/fuzz.cpp index 5b75c22e6..3679a95ec 100644 --- a/src/test_lib_json/fuzz.cpp +++ b/src/test_lib_json/fuzz.cpp @@ -11,10 +11,6 @@ #include #include -namespace Json { -class Exception; -} - extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { Json::CharReaderBuilder builder; @@ -45,10 +41,14 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { Json::Value root; const auto data_str = reinterpret_cast(data); +#if JSON_USE_EXCEPTION try { +#endif // JSON_USE_EXCEPTION reader->parse(data_str, data_str + size, &root, nullptr); +#if JSON_USE_EXCEPTION } catch (Json::Exception const&) { } +#endif // JSON_USE_EXCEPTION // Whether it succeeded or not doesn't matter. return 0; } diff --git a/src/test_lib_json/jsontest.h b/src/test_lib_json/jsontest.h index 69e3264b9..3652c4029 100644 --- a/src/test_lib_json/jsontest.h +++ b/src/test_lib_json/jsontest.h @@ -228,6 +228,8 @@ TestResult& checkStringEqual(TestResult& result, const Json::String& expected, JsonTest::ToJsonString(actual), __FILE__, \ __LINE__, #expected " == " #actual) +#if JSON_USE_EXCEPTION + /// \brief Asserts that a given expression throws an exception #define JSONTEST_ASSERT_THROWS(expr) \ do { \ @@ -242,6 +244,8 @@ TestResult& checkStringEqual(TestResult& result, const Json::String& expected, "expected exception thrown: " #expr); \ } while (0) +#endif // JSON_USE_EXCEPTION + /// \brief Begin a fixture test case. #define JSONTEST_FIXTURE(FixtureType, name) \ class Test##FixtureType##name : public FixtureType { \ diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index 60f149d5e..e20723498 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -1888,7 +1888,7 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, typeChecksThrowExceptions) { JSONTEST_ASSERT_THROWS(objVal.asBool()); JSONTEST_ASSERT_THROWS(arrVal.asBool()); -#endif +#endif // JSON_USE_EXCEPTION } JSONTEST_FIXTURE_LOCAL(ValueTest, offsetAccessors) { @@ -3323,6 +3323,8 @@ JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithDetailError) { } JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithStackLimit) { +#if JSON_USE_EXCEPTION + Json::CharReaderBuilder b; Json::Value root; char const doc[] = R"({ "property" : "value" })"; @@ -3342,6 +3344,8 @@ JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithStackLimit) { JSONTEST_ASSERT_THROWS( reader->parse(doc, doc + std::strlen(doc), &root, &errs)); } + +#endif // JSON_USE_EXCEPTION } JSONTEST_FIXTURE_LOCAL(CharReaderTest, testOperator) { @@ -3961,6 +3965,8 @@ JSONTEST_FIXTURE_LOCAL(IteratorTest, indexes) { } JSONTEST_FIXTURE_LOCAL(IteratorTest, constness) { +#if JSON_USE_EXCEPTION + Json::Value const v; JSONTEST_ASSERT_THROWS( Json::Value::iterator it(v.begin())); // Compile, but throw. @@ -3982,6 +3988,8 @@ JSONTEST_FIXTURE_LOCAL(IteratorTest, constness) { } Json::String expected = R"(" 9","10","11",)"; JSONTEST_ASSERT_STRING_EQUAL(expected, out.str()); + +#endif // JSON_USE_EXCEPTION } struct RValueTest : JsonTest::TestCase {}; From 4bcbc6ac0509cc8b0d0d84618cfd518dd945c114 Mon Sep 17 00:00:00 2001 From: bcsgh <33939446+bcsgh@users.noreply.github.com> Date: Wed, 12 Nov 2025 00:01:15 -0800 Subject: [PATCH 12/56] Add Bazel tests (#1601) * Expose JSON_USE_EXCEPTION and JSON_HAS_INT64 as Bazel config flags with defaults that match the existing Bazel build. Switch //:jsoncpp from using copts ro defines for JSON_USE_EXCEPTION and JSON_HAS_INT64 so that rules that depend on it get the same config. Make src/test_lib_json/fuzz.cpp respect JSON_USE_EXCEPTION. * #ifdef stuff that should only be used with JSON_USE_EXCEPTION. * Modify runjsontests.py to allow passing a single test case file. * Add tests to the Bazel builds. * Reverse the polarity to fix a bug. --------- Co-authored-by: Jordan Bayles --- src/jsontestrunner/BUILD.bazel | 6 ++++ src/test_lib_json/BUILD.bazel | 11 ++++++ test/BUILD.bazel | 20 +++++++++++ test/runjsontests.py | 61 +++++++++++++++++++++------------- 4 files changed, 74 insertions(+), 24 deletions(-) create mode 100644 src/jsontestrunner/BUILD.bazel create mode 100644 src/test_lib_json/BUILD.bazel create mode 100644 test/BUILD.bazel diff --git a/src/jsontestrunner/BUILD.bazel b/src/jsontestrunner/BUILD.bazel new file mode 100644 index 000000000..543bc5d88 --- /dev/null +++ b/src/jsontestrunner/BUILD.bazel @@ -0,0 +1,6 @@ +cc_binary( + name = "jsontestrunner", + srcs = ["main.cpp"], + deps = ["//:jsoncpp"], + visibility = ["//test:__pkg__"], +) diff --git a/src/test_lib_json/BUILD.bazel b/src/test_lib_json/BUILD.bazel new file mode 100644 index 000000000..7e83f5219 --- /dev/null +++ b/src/test_lib_json/BUILD.bazel @@ -0,0 +1,11 @@ +cc_test( + name = "jsoncpp_test", + srcs = [ + "jsontest.cpp", + "jsontest.h", + "main.cpp", + "fuzz.h", + "fuzz.cpp", + ], + deps = ["//:jsoncpp"], +) diff --git a/test/BUILD.bazel b/test/BUILD.bazel new file mode 100644 index 000000000..269cd8646 --- /dev/null +++ b/test/BUILD.bazel @@ -0,0 +1,20 @@ +filegroup( + name = "expected", + srcs = glob(["data/**", "jsonchecker/**"], exclude=["**/*.json"]), +) + +[py_test( + name = "runjson_%s_test" % "_".join(f.split("/")), + srcs = ["runjsontests.py"], + main = "runjsontests.py", + args = [ + "--with-json-checker", + "$(location //src/jsontestrunner:jsontestrunner)", + "$(location :%s)" % f, + ], + data = [ + "//src/jsontestrunner:jsontestrunner", + ":expected", + ":%s" % f, + ], +) for f in glob(["**/*.json"])] diff --git a/test/runjsontests.py b/test/runjsontests.py index 49cc7a960..14275ec22 100644 --- a/test/runjsontests.py +++ b/test/runjsontests.py @@ -66,38 +66,51 @@ class FailError(Exception): def __init__(self, msg): super(Exception, self).__init__(msg) -def runAllTests(jsontest_executable_path, input_dir = None, +def runAllTests(jsontest_executable_path, input_path = None, use_valgrind=False, with_json_checker=False, writerClass='StyledWriter'): - if not input_dir: - input_dir = os.path.join(os.getcwd(), 'data') - tests = glob(os.path.join(input_dir, '*.json')) - if with_json_checker: - all_tests = glob(os.path.join(input_dir, '../jsonchecker', '*.json')) - # These tests fail with strict json support, but pass with JsonCPP's - # extra leniency features. When adding a new exclusion to this list, - # remember to add the test's number and reasoning here: - known = ["fail{}.json".format(n) for n in [ - 4, 9, # fail because we allow trailing commas - 7, # fails because we allow commas after close - 8, # fails because we allow extra close - 10, # fails because we allow extra values after close - 13, # fails because we allow leading zeroes in numbers - 18, # fails because we allow deeply nested values - 25, # fails because we allow tab characters in strings - 27, # fails because we allow string line breaks - ]] - test_jsonchecker = [ test for test in all_tests - if os.path.basename(test) not in known] + if not input_path: + input_path = os.path.join(os.getcwd(), 'data') + if os.path.isdir(input_path): + tests = [ + os.path.normpath(os.path.abspath(test)) + for test in glob(os.path.join(input_path, '*.json')) + ] + + if with_json_checker: + tests += [ + os.path.normpath(os.path.abspath(test)) + for test in glob(os.path.join(input_path, '../jsonchecker', '*.json')) + ] else: - test_jsonchecker = [] + tests = [input_path] + + # These tests fail with strict json support, but pass with JsonCPP's + # extra leniency features. When adding a new exclusion to this list, + # remember to add the test's number and reasoning here: + known = ["fail{}.json".format(n) for n in [ + 4, 9, # fail because we allow trailing commas + 7, # fails because we allow commas after close + 8, # fails because we allow extra close + 10, # fails because we allow extra values after close + 13, # fails because we allow leading zeroes in numbers + 18, # fails because we allow deeply nested values + 25, # fails because we allow tab characters in strings + 27, # fails because we allow string line breaks + ]] + + tests = [ + test for test in tests + if os.path.basename(test) not in known or + os.path.basename(os.path.dirname(test)) != "jsonchecker" + ] failed_tests = [] valgrind_path = use_valgrind and VALGRIND_CMD or '' - for input_path in tests + test_jsonchecker: + for input_path in tests: expect_failure = os.path.basename(input_path).startswith('fail') - is_json_checker_test = input_path in test_jsonchecker + is_json_checker_test = os.path.basename(os.path.dirname(input_path)) == "jsonchecker" is_parse_only = is_json_checker_test or expect_failure is_strict_test = ('_strict_' in os.path.basename(input_path)) or is_json_checker_test print('TESTING:', input_path, end=' ') From 30e7528001e17d0a03e33797010b146ae1ec2d89 Mon Sep 17 00:00:00 2001 From: Hong Xu Date: Wed, 12 Nov 2025 00:07:06 -0800 Subject: [PATCH 13/56] Return false in Reader::readValue when stack limit is exceeded (#1619) jsoncpp, as a shared library, should not call `abort` merely because there's an error reading a value. See https://en.cppreference.com/w/c/program/abort, `abort` should only be called to **abnormally** cause the program to exit. Functions inserted by `atexit` are also not called, meaning that the host program may have not cleaned up resources properly. But here, exceeding stack limit isn't a sign of abnormalty. `exit` is not a good substitute either, see the `exit-in-shared-library` from Debian: https://lintian.debian.org/tags/exit-in-shared-library.html Fix #1618 In this case, returning false seems like a better idea. Co-authored-by: Jordan Bayles --- src/lib_json/json_reader.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 93bb82240..265b03054 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -144,7 +144,12 @@ bool Reader::readValue() { // after calling readValue(). parse() executes one nodes_.push(), so > instead // of >=. if (nodes_.size() > stackLimit_g) +#if JSON_USE_EXCEPTION throwRuntimeError("Exceeded stackLimit in readValue()."); +#else + // throwRuntimeError aborts. Don't abort here. + return false; +#endif Token token; readTokenSkippingComments(token); From e16663cc02ac1dc38f59e8a61d00fd5bcc24bc7d Mon Sep 17 00:00:00 2001 From: bmagistro Date: Wed, 12 Nov 2025 03:10:33 -0500 Subject: [PATCH 14/56] Remove deprecated/removed clang-tidy key AnalyzeTemporaryDtors (#1614) (#1615) Co-authored-by: Ben Magistro Co-authored-by: Jordan Bayles --- .clang-tidy | 1 - 1 file changed, 1 deletion(-) diff --git a/.clang-tidy b/.clang-tidy index 99e914df9..75da71785 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,7 +2,6 @@ Checks: 'google-readability-casting,modernize-deprecated-headers,modernize-loop-convert,modernize-use-auto,modernize-use-default-member-init,modernize-use-using,readability-else-after-return,readability-redundant-member-init,readability-redundant-string-cstr' WarningsAsErrors: '' HeaderFilterRegex: '' -AnalyzeTemporaryDtors: false FormatStyle: none CheckOptions: - key: modernize-use-using.IgnoreMacros From b511d9e64956db998b74909df112ac8c8f41d6ff Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Wed, 12 Nov 2025 09:14:04 +0100 Subject: [PATCH 15/56] [docs] Consuming JSONCpp via Conan package manager (#1622) * Fix Conan badge URL Signed-off-by: Uilian Ries * Add Conan instructions in README Signed-off-by: Uilian Ries * Add build missing Signed-off-by: Uilian Ries --------- Signed-off-by: Uilian Ries Co-authored-by: Jordan Bayles --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5bff8dca2..c8b42a9be 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # JsonCpp -[![badge](https://img.shields.io/badge/conan.io-jsoncpp%2F1.8.0-green.svg?logo=data:image/png;base64%2CiVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAA1VBMVEUAAABhlctjlstkl8tlmMtlmMxlmcxmmcxnmsxpnMxpnM1qnc1sn85voM91oM11oc1xotB2oc56pNF6pNJ2ptJ8ptJ8ptN9ptN8p9N5qNJ9p9N9p9R8qtOBqdSAqtOAqtR%2BrNSCrNJ/rdWDrNWCsNWCsNaJs9eLs9iRvNuVvdyVv9yXwd2Zwt6axN6dxt%2Bfx%2BChyeGiyuGjyuCjyuGly%2BGlzOKmzOGozuKoz%2BKqz%2BOq0OOv1OWw1OWw1eWx1eWy1uay1%2Baz1%2Baz1%2Bez2Oe02Oe12ee22ujUGwH3AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgBQkREyOxFIh/AAAAiklEQVQI12NgAAMbOwY4sLZ2NtQ1coVKWNvoc/Eq8XDr2wB5Ig62ekza9vaOqpK2TpoMzOxaFtwqZua2Bm4makIM7OzMAjoaCqYuxooSUqJALjs7o4yVpbowvzSUy87KqSwmxQfnsrPISyFzWeWAXCkpMaBVIC4bmCsOdgiUKwh3JojLgAQ4ZCE0AMm2D29tZwe6AAAAAElFTkSuQmCC)](https://bintray.com/theirix/conan-repo/jsoncpp%3Atheirix) +[![Conan Center](https://img.shields.io/conan/v/jsoncpp)](https://conan.io/center/recipes/jsoncpp) [![badge](https://img.shields.io/badge/license-MIT-blue)](https://github.com/open-source-parsers/jsoncpp/blob/master/LICENSE) [![badge](https://img.shields.io/badge/document-doxygen-brightgreen)](http://open-source-parsers.github.io/jsoncpp-docs/doxygen/index.html) [![Coverage Status](https://coveralls.io/repos/github/open-source-parsers/jsoncpp/badge.svg?branch=master)](https://coveralls.io/github/open-source-parsers/jsoncpp?branch=master) @@ -52,6 +52,14 @@ You can download and install JsonCpp using the [vcpkg](https://github.com/Micros The JsonCpp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. +### Conan package manager + +You can download and install JsonCpp using the [Conan](https://conan.io/) package manager: + + conan install -r conancenter --requires="jsoncpp/[*]" --build=missing + +The JsonCpp package in Conan Center is kept up to date by [ConanCenterIndex](https://github.com/conan-io/conan-center-index) contributors. If the version is out of date, please create an issue or pull request on the Conan Center Index repository. + ### Amalgamated source https://github.com/open-source-parsers/jsoncpp/wiki/Amalgamated-(Possibly-outdated) From 32f924ffb01abf7efb0b56f100932505e882c099 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Feb 2026 12:58:19 -0800 Subject: [PATCH 16/56] Cleanup README.md, fix broken link. (#1633) * Cleanup README.md, fix broken link. * cleanup readme vcpkg notes --- README.md | 99 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index c8b42a9be..fbdd0bee6 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ [![badge](https://img.shields.io/badge/document-doxygen-brightgreen)](http://open-source-parsers.github.io/jsoncpp-docs/doxygen/index.html) [![Coverage Status](https://coveralls.io/repos/github/open-source-parsers/jsoncpp/badge.svg?branch=master)](https://coveralls.io/github/open-source-parsers/jsoncpp?branch=master) - [JSON][json-org] is a lightweight data-interchange format. It can represent numbers, strings, ordered sequences of values, and collections of name/value pairs. @@ -14,10 +13,9 @@ pairs. JsonCpp is a C++ library that allows manipulating JSON values, including serialization and deserialization to and from strings. It can also preserve -existing comment in unserialization/serialization steps, making it a convenient +existing comment in deserialization/serialization steps, making it a convenient format to store user input files. - ## Documentation [JsonCpp documentation][JsonCpp-documentation] is generated using [Doxygen][]. @@ -25,7 +23,6 @@ format to store user input files. [JsonCpp-documentation]: http://open-source-parsers.github.io/jsoncpp-docs/doxygen/index.html [Doxygen]: http://www.doxygen.org - ## A note on backward-compatibility * `1.y.z` is built with C++11. @@ -34,42 +31,106 @@ format to store user input files. * Major versions maintain binary-compatibility. ### Special note -The branch `00.11.z`is a new branch, its major version number `00` is to show that it is -different from `0.y.z` and `1.y.z`, the main purpose of this branch is to make a balance -between the other two branches. Thus, users can use some new features in this new branch -that introduced in 1.y.z, but can hardly applied into 0.y.z. + +The branch `00.11.z`is a new branch, its major version number `00` is to show +that it is different from `0.y.z` and `1.y.z`, the main purpose of this branch +is to make a balance between the other two branches. Thus, users can use some +new features in this new branch that introduced in 1.y.z, but can hardly applied +into 0.y.z. ## Using JsonCpp in your project ### The vcpkg dependency manager -You can download and install JsonCpp using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager: +You can download and install JsonCpp using the [vcpkg](https://github.com/Microsoft/vcpkg/) +dependency manager, which has installation instruction dependent on your +build system. For example, if you are in a CMake project, the +[CMake install tutorial](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started?pivots=shell-powershell) +suggests the follow installation method. + +First, clone and set up `vcpkg`. + +```sh git clone https://github.com/Microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.sh - ./vcpkg integrate install - ./vcpkg install jsoncpp +``` + +Then, create a [vcpkg.json manifest](https://learn.microsoft.com/en-us/vcpkg/reference/vcpkg-json), +enabling manifest mode and adding JsonCpp to the manifest's dependencies list. + +```sh + vcpkg new --application + vcpkg add port jsoncpp +``` + +> [!NOTE]: you can use vcpkg in either classic mode or manifest mode (recommended). + +#### Classic mode -The JsonCpp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. +If your project does not have a `vcpkg.json`, +your project is in [Classic mode](https://learn.microsoft.com/en-us/vcpkg/concepts/classic-mode) +you can install JsonCpp by directly invoking the `install` command: + +```sh + vcpkg install jsoncpp +``` + +### Manifest mode + +If your project *does* have a vcpkg.json manifest, your project is in [Manifest mode](https://learn.microsoft.com/en-us/vcpkg/concepts/manifest-mode) +and you need to add JsonCpp to your package manifest dependencies, then invoke +install with no arguments. + +```sh + vcpkg add port jsoncpp + vcpkg install +``` + +Example manifest: + +```sh +{ + "name": "best-app-ever", + "dependencies": [ "jsoncpp" ], +} +``` + +> [!NOTE] The JsonCpp port in vcpkg is kept up to date by Microsoft team members and community contributors. +> If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) +> on the vcpkg repository. ### Conan package manager -You can download and install JsonCpp using the [Conan](https://conan.io/) package manager: +You can download and install JsonCpp using the [Conan](https://conan.io/) +package manager: +```sh conan install -r conancenter --requires="jsoncpp/[*]" --build=missing +``` -The JsonCpp package in Conan Center is kept up to date by [ConanCenterIndex](https://github.com/conan-io/conan-center-index) contributors. If the version is out of date, please create an issue or pull request on the Conan Center Index repository. +The JsonCpp package in Conan Center is kept up to date by [ConanCenterIndex](https://github.com/conan-io/conan-center-index) +contributors. If the version is out of date, please create an issue or pull request on the +Conan Center Index repository. ### Amalgamated source -https://github.com/open-source-parsers/jsoncpp/wiki/Amalgamated-(Possibly-outdated) + +See the [Wiki entry on Amalgamated Source](https://github.com/open-source-parsers/jsoncpp/wiki/Amalgamated-(Possibly-outdated)). ### The Meson Build System -If you are using the [Meson Build System](http://mesonbuild.com), then you can get a wrap file by downloading it from [Meson WrapDB](https://wrapdb.mesonbuild.com/jsoncpp), or simply use `meson wrap install jsoncpp`. + +If you are using the [Meson Build System](http://mesonbuild.com), then you can +get a wrap file by downloading it from [Meson WrapDB](https://mesonbuild.com/Wrapdb-projects.html), +or simply use `meson wrap install jsoncpp`. ### Other ways -If you have trouble, see the [Wiki](https://github.com/open-source-parsers/jsoncpp/wiki), or post a question as an Issue. + +If you have trouble, see the +[Wiki](https://github.com/open-source-parsers/jsoncpp/wiki), or post a question +as an Issue. ## License -See the `LICENSE` file for details. In summary, JsonCpp is licensed under the -MIT license, or public domain if desired and recognized in your jurisdiction. +See the [LICENSE](./LICENSE) file for details. In summary, JsonCpp is licensed +under the MIT license, or public domain if desired and recognized in your +jurisdiction. From e799ca052df0f859d8d4133211344581c211b925 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Tue, 3 Feb 2026 11:54:59 -0800 Subject: [PATCH 17/56] Add gcovr.cfg to fix CI coverage merge errors (#1635) Configures gcovr to use 'merge-mode-functions = separate', resolving the GcovrMergeAssertionError caused by strict merging of header-only functions in `jsontest.h`. Also adds source filtering to focus reports on `src/lib_json/` and `include/json/`, and excludes throw branches to reduce false positives. --- gcovr.cfg | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 gcovr.cfg diff --git a/gcovr.cfg b/gcovr.cfg new file mode 100644 index 000000000..ffbea3656 --- /dev/null +++ b/gcovr.cfg @@ -0,0 +1,22 @@ +# Newer versions of gcovr have strict function merging by default, which +# can cause issues with header-only functions or macros (like in jsontest.h). +# 'separate' mode keeps them distinct, fixing the GcovrMergeAssertionError. +merge-mode-functions = separate + +# --- Filtering --- +# Only include the library sources in the coverage report. +# This ensures coverage stats reflect the library quality and ignores test code. +filter = src/lib_json/ +filter = include/json/ + +# Exclude the build directory to avoid processing generated files +exclude-directories = build + +# --- Noise Reduction --- +# Ignore branches that are generated by the compiler (e.g., exception handling) +# This drastically reduces "false positives" for missing branch coverage. +exclude-throw-branches = yes + +# --- CI Visibility --- +# Print a small summary table to the console logs. +print-summary = yes From 2b3cc5a6ebd63ee8ee5e5b25e50947d3de35e6a4 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 17:37:00 -0800 Subject: [PATCH 18/56] Remove build directory exclusion from gcovr config (#1640) Remove exclusion of the build directory from coverage. --- gcovr.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/gcovr.cfg b/gcovr.cfg index ffbea3656..621e71053 100644 --- a/gcovr.cfg +++ b/gcovr.cfg @@ -9,9 +9,6 @@ merge-mode-functions = separate filter = src/lib_json/ filter = include/json/ -# Exclude the build directory to avoid processing generated files -exclude-directories = build - # --- Noise Reduction --- # Ignore branches that are generated by the compiler (e.g., exception handling) # This drastically reduces "false positives" for missing branch coverage. From 4188925158cd4d88105ba36ec5b4d56cf098085b Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 18:11:03 -0800 Subject: [PATCH 19/56] Add workflow for version bumping --- .github/workflows/version-bump | 60 ++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/version-bump diff --git a/.github/workflows/version-bump b/.github/workflows/version-bump new file mode 100644 index 000000000..b60c7b967 --- /dev/null +++ b/.github/workflows/version-bump @@ -0,0 +1,60 @@ +name: "Bump Project Version" + +on: + workflow_dispatch: + inputs: + version_base: + description: 'Target Version (e.g., 1.9.7)' + required: true + default: '1.9.7' + +jobs: + bump-version: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Construct Version String + id: get_ver + run: | + BASE="${{ github.event.inputs.version_base }}" + RUN_NUM="${{ github.run_number }}" + FULL_VER="$BASE.$RUN_NUM" + + echo "full_version=$FULL_VER" >> $GITHUB_OUTPUT + echo "base_version=$BASE" >> $GITHUB_OUTPUT + + - name: Update Project Files + run: | + VER="${{ steps.get_ver.outputs.full_version }}" + echo "Bumping all files to $VER" + + # 1. CMakeLists.txt (Matches 'VERSION X.Y.Z.W' or 'X.Y.Z') + sed -i "s/VERSION [0-9.]*/VERSION $VER/" CMakeLists.txt + + # 2. meson.build + sed -i "s/version : '[0-9.]*'/version : '$VER'/" meson.build + + # 3. vcpkg.json + jq ".version = \"$VER\"" vcpkg.json > vcpkg.json.tmp && mv vcpkg.json.tmp vcpkg.json + + # 4. include/json/version.h + # We parse the base version for the numeric macros (MAJOR.MINOR.PATCH) + # while the 4-part string goes into JSONCPP_VERSION_STRING. + IFS='.' read -r MAJOR MINOR PATCH <<< "${{ steps.get_ver.outputs.base_version }}" + + 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 + + - name: Commit and Push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add . + git commit -m "chore: bump version to ${{ steps.get_ver.outputs.full_version }} (Run #${{ github.run_number }})" + git push From 257e15aa31885d6f378b1be5094da759326efb5e Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 18:11:42 -0800 Subject: [PATCH 20/56] Add version-bump workflow configuration --- .github/workflows/{version-bump => version-bump.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{version-bump => version-bump.yml} (100%) diff --git a/.github/workflows/version-bump b/.github/workflows/version-bump.yml similarity index 100% rename from .github/workflows/version-bump rename to .github/workflows/version-bump.yml From 3b8f743eeca2e164fc80a32f6ee3078f12aa39fc Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 18:18:31 -0800 Subject: [PATCH 21/56] Rename version-bump.yml to update-project-version.yml --- .github/workflows/update-project-version.yml | 59 +++++++++++++++++++ .github/workflows/version-bump.yml | 60 -------------------- 2 files changed, 59 insertions(+), 60 deletions(-) create mode 100644 .github/workflows/update-project-version.yml delete mode 100644 .github/workflows/version-bump.yml diff --git a/.github/workflows/update-project-version.yml b/.github/workflows/update-project-version.yml new file mode 100644 index 000000000..fc4d48689 --- /dev/null +++ b/.github/workflows/update-project-version.yml @@ -0,0 +1,59 @@ +name: "update project version" + +on: + workflow_dispatch: + inputs: + target_version: + description: 'Next Version (e.g., 1.9.7)' + required: true + default: '1.9.7' + +jobs: + create-bump-pr: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Update Project Files + id: update_files + run: | + VER="${{ github.event.inputs.target_version }}" + echo "Updating files to $VER" + + # 1. CMakeLists.txt + sed -i "s/VERSION [0-9.]*/VERSION $VER/" CMakeLists.txt + + # 2. meson.build + sed -i "s/version : '[0-9.]*'/version : '$VER'/" meson.build + + # 3. vcpkg.json (Using jq for safety) + jq ".version = \"$VER\"" vcpkg.json > vcpkg.json.tmp && mv vcpkg.json.tmp vcpkg.json + + # 4. include/json/version.h + # Parse X.Y.Z into separate variables + IFS='.' read -r MAJOR MINOR PATCH <<< "$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 + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore: bump version to ${{ github.event.inputs.target_version }}" + branch: "bump-to-${{ github.event.inputs.target_version }}" + title: "chore: bump version to ${{ github.event.inputs.target_version }}" + body: | + Manual version bump to `${{ github.event.inputs.target_version }}` to start the next development cycle. + + **Files updated:** + - `CMakeLists.txt` + - `meson.build` + - `vcpkg.json` + - `include/json/version.h` + labels: "maintenance" diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml deleted file mode 100644 index b60c7b967..000000000 --- a/.github/workflows/version-bump.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: "Bump Project Version" - -on: - workflow_dispatch: - inputs: - version_base: - description: 'Target Version (e.g., 1.9.7)' - required: true - default: '1.9.7' - -jobs: - bump-version: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Construct Version String - id: get_ver - run: | - BASE="${{ github.event.inputs.version_base }}" - RUN_NUM="${{ github.run_number }}" - FULL_VER="$BASE.$RUN_NUM" - - echo "full_version=$FULL_VER" >> $GITHUB_OUTPUT - echo "base_version=$BASE" >> $GITHUB_OUTPUT - - - name: Update Project Files - run: | - VER="${{ steps.get_ver.outputs.full_version }}" - echo "Bumping all files to $VER" - - # 1. CMakeLists.txt (Matches 'VERSION X.Y.Z.W' or 'X.Y.Z') - sed -i "s/VERSION [0-9.]*/VERSION $VER/" CMakeLists.txt - - # 2. meson.build - sed -i "s/version : '[0-9.]*'/version : '$VER'/" meson.build - - # 3. vcpkg.json - jq ".version = \"$VER\"" vcpkg.json > vcpkg.json.tmp && mv vcpkg.json.tmp vcpkg.json - - # 4. include/json/version.h - # We parse the base version for the numeric macros (MAJOR.MINOR.PATCH) - # while the 4-part string goes into JSONCPP_VERSION_STRING. - IFS='.' read -r MAJOR MINOR PATCH <<< "${{ steps.get_ver.outputs.base_version }}" - - 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 - - - name: Commit and Push - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add . - git commit -m "chore: bump version to ${{ steps.get_ver.outputs.full_version }} (Run #${{ github.run_number }})" - git push From fb3e7504ac9749c27c5510ccfff9089bd062d51d Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 18:22:46 -0800 Subject: [PATCH 22/56] Refactor version bump workflow for clarity and functionality Updated the workflow name and added target_soversion input. Modified version update commands for CMakeLists.txt, meson.build, and include/json/version.h. --- .github/workflows/update-project-version.yml | 59 +++++++++----------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/.github/workflows/update-project-version.yml b/.github/workflows/update-project-version.yml index fc4d48689..6e284b151 100644 --- a/.github/workflows/update-project-version.yml +++ b/.github/workflows/update-project-version.yml @@ -1,4 +1,4 @@ -name: "update project version" +name: "Manual Version Bump" on: workflow_dispatch: @@ -7,9 +7,12 @@ on: description: 'Next Version (e.g., 1.9.7)' required: true default: '1.9.7' + target_soversion: + description: 'Next SOVERSION (e.g., 28). Leave blank to keep current.' + required: false jobs: - create-bump-pr: + bump-and-verify: runs-on: ubuntu-latest permissions: contents: write @@ -22,38 +25,26 @@ jobs: id: update_files run: | VER="${{ github.event.inputs.target_version }}" - echo "Updating files to $VER" - - # 1. CMakeLists.txt - sed -i "s/VERSION [0-9.]*/VERSION $VER/" CMakeLists.txt - - # 2. meson.build - sed -i "s/version : '[0-9.]*'/version : '$VER'/" meson.build - - # 3. vcpkg.json (Using jq for safety) + SOVER="${{ github.event.inputs.target_soversion }}" + echo "Bumping version to $VER" + + # 1. CMakeLists.txt: Match only the project declaration + # This prevents touching comments, policies, or SOVERSION + sed -i "s/project(jsoncpp VERSION [0-9.]*/project(jsoncpp VERSION $VER/" CMakeLists.txt + + # Only update SOVERSION if provided + if [ -n "$SOVER" ]; then + echo "Bumping SOVERSION to $SOVER" + sed -i "s/set(PROJECT_SOVERSION [0-9]*/set(PROJECT_SOVERSION $SOVER/" CMakeLists.txt + fi + + # 2. meson.build: Match the project line specifically + sed -i "s/project('jsoncpp', 'cpp', version : '[0-9.]*'/project('jsoncpp', 'cpp', version : '$VER'/" meson.build + + # 3. vcpkg.json: Using jq for syntax safety jq ".version = \"$VER\"" vcpkg.json > vcpkg.json.tmp && mv vcpkg.json.tmp vcpkg.json - # 4. include/json/version.h - # Parse X.Y.Z into separate variables + # 4. include/json/version.h: Match specific #define macros IFS='.' read -r MAJOR MINOR PATCH <<< "$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 - - - name: Create Pull Request - uses: peter-evans/create-pull-request@v7 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: "chore: bump version to ${{ github.event.inputs.target_version }}" - branch: "bump-to-${{ github.event.inputs.target_version }}" - title: "chore: bump version to ${{ github.event.inputs.target_version }}" - body: | - Manual version bump to `${{ github.event.inputs.target_version }}` to start the next development cycle. - - **Files updated:** - - `CMakeLists.txt` - - `meson.build` - - `vcpkg.json` - - `include/json/version.h` - labels: "maintenance" + 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]*/ From e7324155432f0c20200ee12178c73d35f96afb05 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 18:23:43 -0800 Subject: [PATCH 23/56] Rename workflow and update descriptions --- .github/workflows/update-project-version.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/update-project-version.yml b/.github/workflows/update-project-version.yml index 6e284b151..a72486eb5 100644 --- a/.github/workflows/update-project-version.yml +++ b/.github/workflows/update-project-version.yml @@ -1,14 +1,14 @@ -name: "Manual Version Bump" +name: "update project version" on: workflow_dispatch: inputs: target_version: - description: 'Next Version (e.g., 1.9.7)' + description: 'next version (e.g., 1.9.7)' required: true default: '1.9.7' target_soversion: - description: 'Next SOVERSION (e.g., 28). Leave blank to keep current.' + description: 'next SOVERSION (e.g., 28) - leave blank to keep current.' required: false jobs: @@ -18,10 +18,10 @@ jobs: contents: write pull-requests: write steps: - - name: Checkout code + - name: checkout code uses: actions/checkout@v4 - - name: Update Project Files + - name: update project files id: update_files run: | VER="${{ github.event.inputs.target_version }}" From 3b558716d00c28a51da2d0cf551b94da936171ec Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 18:26:01 -0800 Subject: [PATCH 24/56] Enhance version update workflow with input validation Updated descriptions for workflow inputs and added checks for file existence before updating version files. --- .github/workflows/update-project-version.yml | 70 +++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/.github/workflows/update-project-version.yml b/.github/workflows/update-project-version.yml index a72486eb5..35caeef23 100644 --- a/.github/workflows/update-project-version.yml +++ b/.github/workflows/update-project-version.yml @@ -4,11 +4,11 @@ on: workflow_dispatch: inputs: target_version: - description: 'next version (e.g., 1.9.7)' + description: 'Next Version (e.g., 1.9.7)' required: true default: '1.9.7' target_soversion: - description: 'next SOVERSION (e.g., 28) - leave blank to keep current.' + description: 'Next SOVERSION (e.g., 28). Leave blank to keep current.' required: false jobs: @@ -28,23 +28,57 @@ jobs: SOVER="${{ github.event.inputs.target_soversion }}" echo "Bumping version to $VER" - # 1. CMakeLists.txt: Match only the project declaration - # This prevents touching comments, policies, or SOVERSION - sed -i "s/project(jsoncpp VERSION [0-9.]*/project(jsoncpp VERSION $VER/" CMakeLists.txt - - # Only update SOVERSION if provided - if [ -n "$SOVER" ]; then - echo "Bumping SOVERSION to $SOVER" - sed -i "s/set(PROJECT_SOVERSION [0-9]*/set(PROJECT_SOVERSION $SOVER/" CMakeLists.txt + # 1. CMakeLists.txt + if [ -f CMakeLists.txt ]; then + echo "updating cmakelists.txt" + sed -i "s/project(jsoncpp VERSION [0-9.]*/project(jsoncpp VERSION $VER/" CMakeLists.txt + if [ -n "$SOVER" ]; then + sed -i "s/set(PROJECT_SOVERSION [0-9]*/set(PROJECT_SOVERSION $SOVER/" CMakeLists.txt + fi fi - # 2. meson.build: Match the project line specifically - sed -i "s/project('jsoncpp', 'cpp', version : '[0-9.]*'/project('jsoncpp', 'cpp', version : '$VER'/" meson.build + # 2. meson.build + if [ -f meson.build ]; then + echo "updating meson.build" + sed -i "s/project('jsoncpp', 'cpp', version : '[0-9.]*'/project('jsoncpp', 'cpp', version : '$VER'/" meson.build + 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 + else + echo "vcpkg.json not found, skipping" + fi - # 3. vcpkg.json: Using jq for syntax safety - jq ".version = \"$VER\"" vcpkg.json > vcpkg.json.tmp && mv vcpkg.json.tmp vcpkg.json + # 4. include/json/version.h + if [ -f include/json/version.h ]; then + echo "updating version.h" + IFS='.' read -r MAJOR MINOR PATCH <<< "$VER" + # Using '|' as delimiter to prevent shell escaping issues + 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 + fi + + - name: sanity check (cmake configure) + run: | + if [ -f CMakeLists.txt ]; then + mkdir build_check + cd build_check + cmake .. -DJSONCPP_WITH_TESTS=OFF -DJSONCPP_WITH_POST_BUILD_UNITTEST=OFF + fi - # 4. include/json/version.h: Match specific #define macros - IFS='.' read -r MAJOR MINOR PATCH <<< "$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]*/ + - name: create pull request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore: bump version to ${{ github.event.inputs.target_version }}" + branch: "bump-to-${{ github.event.inputs.target_version }}" + title: "chore: bump version to ${{ github.event.inputs.target_version }}" + body: | + automated version bump. + - new version: `${{ github.event.inputs.target_version }}` + - new soversion: `${{ github.event.inputs.target_soversion || 'no change' }}` + labels: "maintenance" From 50dbfd6ffb5006952ceaddf4733ddadfaf9ba1c8 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 18:30:20 -0800 Subject: [PATCH 25/56] Update version input descriptions and handling --- .github/workflows/update-project-version.yml | 21 ++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/update-project-version.yml b/.github/workflows/update-project-version.yml index 35caeef23..afbf78e65 100644 --- a/.github/workflows/update-project-version.yml +++ b/.github/workflows/update-project-version.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: target_version: - description: 'Next Version (e.g., 1.9.7)' + description: 'Next Version (e.g., 1.9.7 or 1.9.7.123)' required: true default: '1.9.7' target_soversion: @@ -26,7 +26,7 @@ jobs: run: | VER="${{ github.event.inputs.target_version }}" SOVER="${{ github.event.inputs.target_soversion }}" - echo "Bumping version to $VER" + echo "bumping version to $VER" # 1. CMakeLists.txt if [ -f CMakeLists.txt ]; then @@ -47,19 +47,25 @@ jobs: if [ -f vcpkg.json ]; then echo "updating vcpkg.json" jq ".version = \"$VER\"" vcpkg.json > vcpkg.json.tmp && mv vcpkg.json.tmp vcpkg.json - else - echo "vcpkg.json not found, skipping" fi # 4. include/json/version.h if [ -f include/json/version.h ]; then echo "updating version.h" - IFS='.' read -r MAJOR MINOR PATCH <<< "$VER" - # Using '|' as delimiter to prevent shell escaping issues + # Split version into components (handles 3 or 4 parts) + 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 + + # If QUALIFIER exists, append it; otherwise, leave macro empty + if [ -n "$QUALIFIER" ]; then + 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 + fi fi - name: sanity check (cmake configure) @@ -68,6 +74,9 @@ jobs: mkdir build_check cd build_check cmake .. -DJSONCPP_WITH_TESTS=OFF -DJSONCPP_WITH_POST_BUILD_UNITTEST=OFF + cd .. + # CRITICAL: Remove the build folder so it isn't included in the PR + rm -rf build_check fi - name: create pull request From 4bf4e7fcb4c1181be6ae0e3a32ba7e2b7cee1a99 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 18:37:44 -0800 Subject: [PATCH 26/56] Update version descriptions and improve sed commands --- .github/workflows/update-project-version.yml | 24 ++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/update-project-version.yml b/.github/workflows/update-project-version.yml index afbf78e65..b112ce3ae 100644 --- a/.github/workflows/update-project-version.yml +++ b/.github/workflows/update-project-version.yml @@ -4,11 +4,11 @@ on: workflow_dispatch: inputs: target_version: - description: 'Next Version (e.g., 1.9.7 or 1.9.7.123)' + description: 'next version (e.g., 1.9.7 or 1.9.7.123)' required: true default: '1.9.7' target_soversion: - description: 'Next SOVERSION (e.g., 28). Leave blank to keep current.' + description: 'next soversion (e.g., 28). leave blank to keep current.' required: false jobs: @@ -28,19 +28,25 @@ jobs: SOVER="${{ github.event.inputs.target_soversion }}" echo "bumping version to $VER" - # 1. CMakeLists.txt + # 1. CMakeLists.txt (handles multi-line project() call) if [ -f CMakeLists.txt ]; then echo "updating cmakelists.txt" - sed -i "s/project(jsoncpp VERSION [0-9.]*/project(jsoncpp VERSION $VER/" CMakeLists.txt + # match VERSION only if it follows 'project(jsoncpp' + 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 fi - # 2. meson.build + # 2. meson.build (handles multi-line project() and lowercase soversion) if [ -f meson.build ]; then echo "updating meson.build" - sed -i "s/project('jsoncpp', 'cpp', version : '[0-9.]*'/project('jsoncpp', 'cpp', version : '$VER'/" meson.build + # update version in project() + sed -i "/project('jsoncpp'/,/)/ s/version : '[0-9.]*'/version : '$VER'/" meson.build + if [ -n "$SOVER" ]; then + # update soversion in library() + sed -i "s/soversion : [0-9]*/soversion : $SOVER/" meson.build + fi fi # 3. vcpkg.json @@ -52,7 +58,8 @@ jobs: # 4. include/json/version.h if [ -f include/json/version.h ]; then echo "updating version.h" - # Split version into components (handles 3 or 4 parts) + # split version into components (e.g., 1.9.7.123 -> MAJOR=1, MINOR=9, PATCH=7, QUALIFIER=123) + # if 1.9.7 -> QUALIFIER is empty IFS='.' read -r MAJOR MINOR PATCH QUALIFIER <<< "$VER" sed -i "s|#define JSONCPP_VERSION_STRING \"[^\"]*\"|#define JSONCPP_VERSION_STRING \"$VER\"|" include/json/version.h @@ -60,7 +67,7 @@ jobs: 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 QUALIFIER exists, append it; otherwise, leave macro empty + # handle qualifier macro if [ -n "$QUALIFIER" ]; then sed -i "s|#define JSONCPP_VERSION_QUALIFIER.*|#define JSONCPP_VERSION_QUALIFIER $QUALIFIER|" include/json/version.h else @@ -75,7 +82,6 @@ jobs: cd build_check cmake .. -DJSONCPP_WITH_TESTS=OFF -DJSONCPP_WITH_POST_BUILD_UNITTEST=OFF cd .. - # CRITICAL: Remove the build folder so it isn't included in the PR rm -rf build_check fi From b52dce4e2ba4bc55d268523b4cd405990109a3ad Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 18:42:21 -0800 Subject: [PATCH 27/56] Update versioning logic in workflow configuration --- .github/workflows/update-project-version.yml | 22 +++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/.github/workflows/update-project-version.yml b/.github/workflows/update-project-version.yml index b112ce3ae..9a00d255f 100644 --- a/.github/workflows/update-project-version.yml +++ b/.github/workflows/update-project-version.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: target_version: - description: 'next version (e.g., 1.9.7 or 1.9.7.123)' + description: 'next version (e.g., 1.9.7 or 1.9.7.12)' required: true default: '1.9.7' target_soversion: @@ -28,24 +28,24 @@ jobs: SOVER="${{ github.event.inputs.target_soversion }}" echo "bumping version to $VER" - # 1. CMakeLists.txt (handles multi-line project() call) + # 1. CMakeLists.txt if [ -f CMakeLists.txt ]; then echo "updating cmakelists.txt" - # match VERSION only if it follows 'project(jsoncpp' - sed -i "/project(jsoncpp/,/)/ s/VERSION [0-9.]*/VERSION $VER/" CMakeLists.txt + # match VERSION only within the project block + sed -i "/project[[:space:]]*([[:space:]]*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 fi - # 2. meson.build (handles multi-line project() and lowercase soversion) + # 2. meson.build if [ -f meson.build ]; then echo "updating meson.build" - # update version in project() - sed -i "/project('jsoncpp'/,/)/ s/version : '[0-9.]*'/version : '$VER'/" meson.build + # match version : 'x.y.z' with flexible quotes and spacing + sed -i "s/version[[:space:]]*:[[:space:]]*['\"][0-9.]*['\"]/version : '$VER'/" meson.build if [ -n "$SOVER" ]; then - # update soversion in library() - sed -i "s/soversion : [0-9]*/soversion : $SOVER/" meson.build + # matches soversion : '26' + sed -i "s/soversion[[:space:]]*:[[:space:]]*['\"][0-9]*['\"]/soversion : '$SOVER'/" meson.build fi fi @@ -58,8 +58,7 @@ jobs: # 4. include/json/version.h if [ -f include/json/version.h ]; then echo "updating version.h" - # split version into components (e.g., 1.9.7.123 -> MAJOR=1, MINOR=9, PATCH=7, QUALIFIER=123) - # if 1.9.7 -> QUALIFIER is empty + # 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 @@ -67,7 +66,6 @@ jobs: 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 - # handle qualifier macro if [ -n "$QUALIFIER" ]; then sed -i "s|#define JSONCPP_VERSION_QUALIFIER.*|#define JSONCPP_VERSION_QUALIFIER $QUALIFIER|" include/json/version.h else From c4a1e4ccf8f2d7e3d8801b04eae1c76700900383 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 18:45:47 -0800 Subject: [PATCH 28/56] Update versioning in update-project-version.yml --- .github/workflows/update-project-version.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/update-project-version.yml b/.github/workflows/update-project-version.yml index 9a00d255f..7f153919f 100644 --- a/.github/workflows/update-project-version.yml +++ b/.github/workflows/update-project-version.yml @@ -31,7 +31,6 @@ jobs: # 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 if [ -n "$SOVER" ]; then sed -i "s/set(PROJECT_SOVERSION [0-9]*/set(PROJECT_SOVERSION $SOVER/" CMakeLists.txt @@ -41,24 +40,28 @@ jobs: # 2. meson.build if [ -f meson.build ]; then echo "updating meson.build" - # match version : 'x.y.z' with flexible quotes and spacing 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 + # 3. MODULE.bazel + if [ -f MODULE.bazel ]; then + echo "updating MODULE.bazel" + # matches: version = "1.9.6" + sed -i "s/version[[:space:]]*=[[:space:]]*['\"][0-9.]*['\"]/version = \"$VER\"/" MODULE.bazel + fi + + # 4. 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 fi - # 4. include/json/version.h + # 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 @@ -66,6 +69,7 @@ jobs: 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 + # set qualifier to the number if 4th part exists, otherwise leave blank if [ -n "$QUALIFIER" ]; then sed -i "s|#define JSONCPP_VERSION_QUALIFIER.*|#define JSONCPP_VERSION_QUALIFIER $QUALIFIER|" include/json/version.h else From 728ad03790d21d31140b530c97dfcce6234ed993 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 18:48:27 -0800 Subject: [PATCH 29/56] Clarify version update comments in workflow script Updated comments in the version bumping script to clarify that only the version inside the project() or module() blocks is updated for CMakeLists.txt, meson.build, and MODULE.bazel. Removed unnecessary version updates for include/json/version.h and related sanity checks. --- .github/workflows/update-project-version.yml | 59 +++----------------- 1 file changed, 9 insertions(+), 50 deletions(-) diff --git a/.github/workflows/update-project-version.yml b/.github/workflows/update-project-version.yml index 7f153919f..16a413dbc 100644 --- a/.github/workflows/update-project-version.yml +++ b/.github/workflows/update-project-version.yml @@ -28,7 +28,7 @@ jobs: SOVER="${{ github.event.inputs.target_soversion }}" echo "bumping version to $VER" - # 1. CMakeLists.txt + # 1. CMakeLists.txt: Only update version inside the project() block if [ -f CMakeLists.txt ]; then echo "updating cmakelists.txt" sed -i "/project[[:space:]]*([[:space:]]*jsoncpp/,/)/ s/VERSION [0-9.]*/VERSION $VER/" CMakeLists.txt @@ -37,65 +37,24 @@ jobs: fi fi - # 2. meson.build + # 2. meson.build: Only update version inside the project() block if [ -f meson.build ]; then echo "updating meson.build" - sed -i "s/version[[:space:]]*:[[:space:]]*['\"][0-9.]*['\"]/version : '$VER'/" meson.build + sed -i "/project('jsoncpp'/,/)/ s/version[[:space:]]*:[[:space:]]*['\"][0-9.]*['\"]/version : '$VER'/" meson.build if [ -n "$SOVER" ]; then + # update soversion only within the library() or where defined sed -i "s/soversion[[:space:]]*:[[:space:]]*['\"][0-9]*['\"]/soversion : '$SOVER'/" meson.build fi fi - # 3. MODULE.bazel + # 3. MODULE.bazel: Only update version inside the module() block if [ -f MODULE.bazel ]; then echo "updating MODULE.bazel" - # matches: version = "1.9.6" - sed -i "s/version[[:space:]]*=[[:space:]]*['\"][0-9.]*['\"]/version = \"$VER\"/" MODULE.bazel + # match range from 'module(' to the first ')' + sed -i "/module(/,/)/ s/version[[:space:]]*=[[:space:]]*['\"][0-9.]*['\"]/version = \"$VER\"/" MODULE.bazel fi - # 4. vcpkg.json + # 4. vcpkg.json: jq is inherently surgical if [ -f vcpkg.json ]; then echo "updating vcpkg.json" - jq ".version = \"$VER\"" vcpkg.json > vcpkg.json.tmp && mv vcpkg.json.tmp vcpkg.json - fi - - # 5. include/json/version.h - if [ -f include/json/version.h ]; then - echo "updating version.h" - 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 - - # set qualifier to the number if 4th part exists, otherwise leave blank - if [ -n "$QUALIFIER" ]; then - 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 - fi - fi - - - name: sanity check (cmake configure) - run: | - if [ -f CMakeLists.txt ]; then - mkdir build_check - cd build_check - cmake .. -DJSONCPP_WITH_TESTS=OFF -DJSONCPP_WITH_POST_BUILD_UNITTEST=OFF - cd .. - rm -rf build_check - fi - - - name: create pull request - uses: peter-evans/create-pull-request@v7 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: "chore: bump version to ${{ github.event.inputs.target_version }}" - branch: "bump-to-${{ github.event.inputs.target_version }}" - title: "chore: bump version to ${{ github.event.inputs.target_version }}" - body: | - automated version bump. - - new version: `${{ github.event.inputs.target_version }}` - - new soversion: `${{ github.event.inputs.target_soversion || 'no change' }}` - labels: "maintenance" + jq ".version = \"$VER\"" vcpkg.json > vcpkg.json.tmp && mv vcpkg.json. From acf3b5dbaca090a53e3bc3f21a52c0052d37c10f Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 18:50:47 -0800 Subject: [PATCH 30/56] Add test for allowDroppedNullPlaceholders (#1648) --- src/test_lib_json/main.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index e20723498..5d5b971f8 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -3095,6 +3095,17 @@ JSONTEST_FIXTURE_LOCAL(ReaderTest, allowNumericKeysTest) { 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) { From 1d5b0b1abecb1b00694a069a8160dc02ab667c06 Mon Sep 17 00:00:00 2001 From: Martin Chang Date: Tue, 3 Mar 2026 10:59:37 +0800 Subject: [PATCH 31/56] prevent test colision when running in parallel via RESOURCE_LOCK (#1637) Co-authored-by: Jordan Bayles --- src/jsontestrunner/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/jsontestrunner/CMakeLists.txt b/src/jsontestrunner/CMakeLists.txt index 1fc71ea87..7f536d318 100644 --- a/src/jsontestrunner/CMakeLists.txt +++ b/src/jsontestrunner/CMakeLists.txt @@ -48,4 +48,9 @@ if(PYTHONINTERP_FOUND) COMMAND "${PYTHON_EXECUTABLE}" -B "${RUNJSONTESTS_PATH}" --with-json-checker $ "${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() From dc45da884eb49636e53329d4776a6be9d80a0b22 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 2 Mar 2026 19:19:30 -0800 Subject: [PATCH 32/56] fixup project version updater (#1649) * fixup project version updater * Enhance version bumping workflow in YAML Updated the versioning script to handle version bumps in various files, including CMakeLists.txt, meson.build, MODULE.bazel, vcpkg.json, and include/json/version.h. Improved echo statements and jq command usage. * fix meson inclusion probably. * Refactor update-project-version workflow Removed sanity check and pull request creation steps from the workflow. * Add version verification and pull request creation steps * Simplify version update workflow Refactor version update logic and remove unnecessary checks. * Refactor version extraction in workflow script * Enhance workflow with CMake sanity check and version bump Add sanity check for CMake configuration and automate version bump process. * Fix regex patterns for version updates in YAML * Refactor version update script for clarity and efficiency * Fix label formatting in update-project-version.yml --- .github/workflows/update-project-version.yml | 96 ++++++++++++++++---- 1 file changed, 80 insertions(+), 16 deletions(-) diff --git a/.github/workflows/update-project-version.yml b/.github/workflows/update-project-version.yml index 16a413dbc..af03a5566 100644 --- a/.github/workflows/update-project-version.yml +++ b/.github/workflows/update-project-version.yml @@ -22,39 +22,103 @@ jobs: 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: Only update version inside the project() block + # 1. cmakelists.txt if [ -f CMakeLists.txt ]; then - echo "updating cmakelists.txt" - 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 fi - # 2. meson.build: Only update version inside the project() block + # 2. meson.build if [ -f meson.build ]; then - echo "updating meson.build" - sed -i "/project('jsoncpp'/,/)/ s/version[[:space:]]*:[[:space:]]*['\"][0-9.]*['\"]/version : '$VER'/" meson.build + # targeting the version line directly + sed -i "s/version[[:space:]]*:[[:space:]]*['\"][0-9.]*['\"]/version : '$VER'/" meson.build if [ -n "$SOVER" ]; then - # update soversion only within the library() or where defined sed -i "s/soversion[[:space:]]*:[[:space:]]*['\"][0-9]*['\"]/soversion : '$SOVER'/" meson.build fi fi - # 3. MODULE.bazel: Only update version inside the module() block + # 3. module.bazel if [ -f MODULE.bazel ]; then - echo "updating MODULE.bazel" - # match range from 'module(' to the first ')' - sed -i "/module(/,/)/ s/version[[:space:]]*=[[:space:]]*['\"][0-9.]*['\"]/version = \"$VER\"/" MODULE.bazel + # 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. vcpkg.json: jq is inherently surgical + # 4. vcpkg.json if [ -f vcpkg.json ]; then - echo "updating vcpkg.json" - jq ".version = \"$VER\"" vcpkg.json > vcpkg.json.tmp && mv vcpkg.json. + 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 + 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 + else + 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 + cmake .. -DJSONCPP_WITH_TESTS=OFF -DJSONCPP_WITH_POST_BUILD_UNITTEST=OFF + cd .. && rm -rf build_check + fi + + - name: create pull request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore: bump version to ${{ github.event.inputs.target_version }}" + branch: "bump-to-${{ github.event.inputs.target_version }}" + title: "chore: bump version to ${{ github.event.inputs.target_version }}" + body: | + 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" From 87139ddc8614767e36969838c18483227dd15318 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Mon, 9 Mar 2026 12:15:08 -0700 Subject: [PATCH 33/56] Update README with project status and focus (#1639) * Update README with project status and focus Add project status and focus areas to README * Update README.md * docs: Restore maintainer disclaimers and fix Conan instructions This commit adds back the warning about community-maintained package managers and the warning about the Amalgamated Source being possibly outdated to prevent misdirected issues. It also includes the necessary generators for Conan 2 in the README's Conan section to resolve issue #1629 while preserving the brevity of the new layout. --- README.md | 130 ++++++++++++++++++++---------------------------------- 1 file changed, 49 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index fbdd0bee6..25b2d2b83 100644 --- a/README.md +++ b/README.md @@ -16,121 +16,89 @@ serialization and deserialization to and from strings. It can also preserve existing comment in deserialization/serialization steps, making it a convenient format to store user input files. -## Documentation +## Project Status -[JsonCpp documentation][JsonCpp-documentation] is generated using [Doxygen][]. +JsonCpp is a mature project in maintenance mode. Our priority is providing a stable, +reliable JSON library for the long tail of C++ development. -[JsonCpp-documentation]: http://open-source-parsers.github.io/jsoncpp-docs/doxygen/index.html -[Doxygen]: http://www.doxygen.org +### Current Focus -## A note on backward-compatibility +* **Security:** Addressing vulnerabilities and fuzzing results. +* **Compatibility:** Ensuring the library builds without warnings on the latest versions of GCC, +Clang, and MSVC. +* **Reliability:** Fixing regressions and critical logical bugs. -* `1.y.z` is built with C++11. -* `0.y.z` can be used with older compilers. -* `00.11.z` can be used both in old and new compilers. -* Major versions maintain binary-compatibility. +### Out of Scope -### Special note +* **Performance:** We are not competing with SIMD-accelerated or reflection-based parsers. +* **Features:** We are generally not accepting requests for new data formats or major API changes. -The branch `00.11.z`is a new branch, its major version number `00` is to show -that it is different from `0.y.z` and `1.y.z`, the main purpose of this branch -is to make a balance between the other two branches. Thus, users can use some -new features in this new branch that introduced in 1.y.z, but can hardly applied -into 0.y.z. +JsonCpp remains a primary choice for developers who require comment preservation and support for +legacy toolchains where modern C++ standards are unavailable. The library is intended to be a +reliable dependency that does not require frequent updates or major migration efforts. -## Using JsonCpp in your project +## A note on backward-compatibility -### The vcpkg dependency manager +* **`1.y.z` (master):** Actively maintained. Requires C++11. -You can download and install JsonCpp using the [vcpkg](https://github.com/Microsoft/vcpkg/) -dependency manager, which has installation instruction dependent on your -build system. For example, if you are in a CMake project, the -[CMake install tutorial](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started?pivots=shell-powershell) -suggests the follow installation method. +* **`0.y.z`:** Legacy support for pre-C++11 compilers. Maintenance is limited to critical security fixes. -First, clone and set up `vcpkg`. +* **`00.11.z`:** Discontinued. -```sh - git clone https://github.com/Microsoft/vcpkg.git - cd vcpkg - ./bootstrap-vcpkg.sh -``` +Major versions maintain binary compatibility. Critical security fixes are accepted for both the `master` and `0.y.z` branches. -Then, create a [vcpkg.json manifest](https://learn.microsoft.com/en-us/vcpkg/reference/vcpkg-json), -enabling manifest mode and adding JsonCpp to the manifest's dependencies list. +## Integration -```sh - vcpkg new --application - vcpkg add port jsoncpp -``` - -> [!NOTE]: you can use vcpkg in either classic mode or manifest mode (recommended). - -#### Classic mode +> [!NOTE] +> Package manager ports (vcpkg, Conan, etc.) are community-maintained. Please report outdated versions or missing generators to their respective repositories. -If your project does not have a `vcpkg.json`, -your project is in [Classic mode](https://learn.microsoft.com/en-us/vcpkg/concepts/classic-mode) -you can install JsonCpp by directly invoking the `install` command: +### vcpkg +Add `jsoncpp` to your `vcpkg.json` manifest: -```sh - vcpkg install jsoncpp +```json +{ + "dependencies": ["jsoncpp"] +} ``` -### Manifest mode +Or install via classic mode: `vcpkg install jsoncpp`. -If your project *does* have a vcpkg.json manifest, your project is in [Manifest mode](https://learn.microsoft.com/en-us/vcpkg/concepts/manifest-mode) -and you need to add JsonCpp to your package manifest dependencies, then invoke -install with no arguments. +### Conan ```sh - vcpkg add port jsoncpp - vcpkg install +conan install --requires="jsoncpp/[*]" --build=missing ``` -Example manifest: - -```sh -{ - "name": "best-app-ever", - "dependencies": [ "jsoncpp" ], -} -``` +If you are using a `conanfile.txt` in a Conan 2 project, ensure you use the appropriate generators: -> [!NOTE] The JsonCpp port in vcpkg is kept up to date by Microsoft team members and community contributors. -> If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) -> on the vcpkg repository. +```ini +[requires] +jsoncpp/[*] -### Conan package manager +[generators] +CMakeToolchain +CMakeDeps +``` -You can download and install JsonCpp using the [Conan](https://conan.io/) -package manager: +### Meson ```sh - conan install -r conancenter --requires="jsoncpp/[*]" --build=missing +meson wrap install jsoncpp ``` -The JsonCpp package in Conan Center is kept up to date by [ConanCenterIndex](https://github.com/conan-io/conan-center-index) -contributors. If the version is out of date, please create an issue or pull request on the -Conan Center Index repository. - ### Amalgamated source -See the [Wiki entry on Amalgamated Source](https://github.com/open-source-parsers/jsoncpp/wiki/Amalgamated-(Possibly-outdated)). +> [!NOTE] +> This approach may be outdated. -### The Meson Build System +For projects requiring a single-header approach, see the [Wiki entry](https://github.com/open-source-parsers/jsoncpp/wiki/Amalgamated-(Possibly-outdated)). -If you are using the [Meson Build System](http://mesonbuild.com), then you can -get a wrap file by downloading it from [Meson WrapDB](https://mesonbuild.com/Wrapdb-projects.html), -or simply use `meson wrap install jsoncpp`. - -### Other ways +## Documentation -If you have trouble, see the -[Wiki](https://github.com/open-source-parsers/jsoncpp/wiki), or post a question -as an Issue. +Documentation is generated via [Doxygen](http://open-source-parsers.github.io/jsoncpp-docs/doxygen/index.html). +Additional information is available on the [Project Wiki](https://github.com/open-source-parsers/jsoncpp/wiki). ## License -See the [LICENSE](./LICENSE) file for details. In summary, JsonCpp is licensed -under the MIT license, or public domain if desired and recognized in your -jurisdiction. +JsonCpp is licensed under the MIT license, or public domain where recognized. +See [LICENSE](./LICENSE) for details. From dbd0850e9ac2b18b91fa5588b1243bbc0eb037c6 Mon Sep 17 00:00:00 2001 From: nv-jdeligiannis <84378738+nv-jdeligiannis@users.noreply.github.com> Date: Tue, 10 Mar 2026 01:33:25 +0100 Subject: [PATCH 34/56] Adding a cmake option to exclude the jsoncpp files from install. (#1596) * Adding a cmake option to exclude the jsoncpp files from install. Useful when used used as a submodule * Updaing help text --------- Co-authored-by: Jordan Bayles --- CMakeLists.txt | 1 + include/CMakeLists.txt | 3 +++ src/lib_json/CMakeLists.txt | 3 +++ 3 files changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ab9c52a2..4eb4499a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,7 @@ option(JSONCPP_WITH_STRICT_ISO "Issue all the warnings demanded by strict ISO C option(JSONCPP_WITH_PKGCONFIG_SUPPORT "Generate and install .pc files" ON) option(JSONCPP_WITH_CMAKE_PACKAGE "Generate and install cmake package files" ON) option(JSONCPP_WITH_EXAMPLE "Compile JsonCpp example" OFF) +option(JSONCPP_WITH_INSTALL "Include JsonCpp header and binaries in the install target" ON) option(JSONCPP_STATIC_WINDOWS_RUNTIME "Use static (MT/MTd) Windows runtime" OFF) option(BUILD_SHARED_LIBS "Build jsoncpp_lib as a shared library." ON) option(BUILD_STATIC_LIBS "Build jsoncpp_lib as a static library." ON) diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index dc40d95e8..bbd640559 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -1,5 +1,8 @@ +if (JSONCPP_WITH_INSTALL) + file(GLOB INCLUDE_FILES "json/*.h") install(FILES ${INCLUDE_FILES} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/json) +endif() \ No newline at end of file diff --git a/src/lib_json/CMakeLists.txt b/src/lib_json/CMakeLists.txt index 3037eb020..86722ac8d 100644 --- a/src/lib_json/CMakeLists.txt +++ b/src/lib_json/CMakeLists.txt @@ -196,6 +196,8 @@ if(BUILD_OBJECT_LIBS) list(APPEND CMAKE_TARGETS ${OBJECT_LIB}) endif() +if (JSONCPP_WITH_INSTALL) + install(TARGETS ${CMAKE_TARGETS} ${INSTALL_EXPORT} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -203,3 +205,4 @@ install(TARGETS ${CMAKE_TARGETS} ${INSTALL_EXPORT} OBJECTS DESTINATION ${CMAKE_INSTALL_LIBDIR} ) +endif() From 715735abbe29ee973049b34a7b6336b93d321284 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Sun, 15 Mar 2026 13:57:22 -0700 Subject: [PATCH 35/56] Change stack depth limit to 256 (#1657) * Change stack depth limit to 256 * run clang format --- src/lib_json/json_reader.cpp | 8 ++++---- src/test_lib_json/main.cpp | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 265b03054..0697132b0 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -39,7 +39,7 @@ // Define JSONCPP_DEPRECATED_STACK_LIMIT as an appropriate integer at compile // time to change the stack limit #if !defined(JSONCPP_DEPRECATED_STACK_LIMIT) -#define JSONCPP_DEPRECATED_STACK_LIMIT 1000 +#define JSONCPP_DEPRECATED_STACK_LIMIT 256 #endif static size_t const stackLimit_g = @@ -1932,7 +1932,7 @@ void CharReaderBuilder::strictMode(Json::Value* settings) { (*settings)["allowDroppedNullPlaceholders"] = false; (*settings)["allowNumericKeys"] = false; (*settings)["allowSingleQuotes"] = false; - (*settings)["stackLimit"] = 1000; + (*settings)["stackLimit"] = 256; (*settings)["failIfExtra"] = true; (*settings)["rejectDupKeys"] = true; (*settings)["allowSpecialFloats"] = false; @@ -1949,7 +1949,7 @@ void CharReaderBuilder::setDefaults(Json::Value* settings) { (*settings)["allowDroppedNullPlaceholders"] = false; (*settings)["allowNumericKeys"] = false; (*settings)["allowSingleQuotes"] = false; - (*settings)["stackLimit"] = 1000; + (*settings)["stackLimit"] = 256; (*settings)["failIfExtra"] = false; (*settings)["rejectDupKeys"] = false; (*settings)["allowSpecialFloats"] = false; @@ -1965,7 +1965,7 @@ void CharReaderBuilder::ecma404Mode(Json::Value* settings) { (*settings)["allowDroppedNullPlaceholders"] = false; (*settings)["allowNumericKeys"] = false; (*settings)["allowSingleQuotes"] = false; - (*settings)["stackLimit"] = 1000; + (*settings)["stackLimit"] = 256; (*settings)["failIfExtra"] = true; (*settings)["rejectDupKeys"] = false; (*settings)["allowSpecialFloats"] = false; diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index 5d5b971f8..f19ca2fb4 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -3355,6 +3355,16 @@ JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithStackLimit) { JSONTEST_ASSERT_THROWS( reader->parse(doc, doc + std::strlen(doc), &root, &errs)); } + // Default stack limit should reject deeply nested input (regression test for + // stack exhaustion from fuzz input like [[[[...]]]]) + { + Json::CharReaderBuilder defaultBuilder; + Json::String nested(300, '['); + CharReaderPtr reader(defaultBuilder.newCharReader()); + Json::String errs; + JSONTEST_ASSERT_THROWS(reader->parse( + nested.data(), nested.data() + nested.size(), &root, &errs)); + } #endif // JSON_USE_EXCEPTION } From 134138d0eb404eedd7b421609e795c858895c0ab Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Sun, 15 Mar 2026 16:33:53 -0700 Subject: [PATCH 36/56] Fix uninitialized CMake variable in version.in (#1658) CMake's project() command sets jsoncpp_VERSION (lowercase prefix), not JSONCPP_VERSION. The wrong variable caused the generated version file to be empty. Fixes #1656 Co-authored-by: Claude Sonnet 4.6 --- version.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.in b/version.in index bfc03f7dd..f1e333570 100644 --- a/version.in +++ b/version.in @@ -1 +1 @@ -@JSONCPP_VERSION@ +@jsoncpp_VERSION@ From ef0877151eb14f2d75fea139b5de34a048733e8b Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Sun, 15 Mar 2026 17:54:08 -0700 Subject: [PATCH 37/56] Fix CMake deprecation warning for compatibility with CMake < 3.10 (#1659) * Fix uninitialized CMake variable in version.in CMake's project() command sets jsoncpp_VERSION (lowercase prefix), not JSONCPP_VERSION. The wrong variable caused the generated version file to be empty. Fixes #1656 Co-Authored-By: Claude Sonnet 4.6 * Fix CMake deprecation warning for compatibility with CMake < 3.10 Bump JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION from 3.8.0 to 3.10.0 to silence the CMake 3.31 deprecation warning. Fixes #1598 --------- Co-authored-by: Claude Sonnet 4.6 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4eb4499a2..cfd1a4e1c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ # CMake versions greater than the JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION policies will # continue to generate policy warnings "CMake Warning (dev)...Policy CMP0XXX is not set:" # -set(JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION "3.8.0") +set(JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION "3.10.0") set(JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION "3.13.2") cmake_minimum_required(VERSION ${JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION}) if("${CMAKE_VERSION}" VERSION_LESS "${JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION}") From 215b0258171aef90a708a7e4a2c40ba19a74a13a Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Sun, 15 Mar 2026 17:57:42 -0700 Subject: [PATCH 38/56] Scope JSON_DLL_BUILD to shared lib target only (#1660) Replaced directory-wide add_compile_definitions/add_definitions with target_compile_definitions PRIVATE on the shared lib target, so JSON_DLL_BUILD is not incorrectly applied to static and object libs. Fixes #1634 --- src/lib_json/CMakeLists.txt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/lib_json/CMakeLists.txt b/src/lib_json/CMakeLists.txt index 86722ac8d..965894158 100644 --- a/src/lib_json/CMakeLists.txt +++ b/src/lib_json/CMakeLists.txt @@ -107,12 +107,6 @@ list(APPEND REQUIRED_FEATURES if(BUILD_SHARED_LIBS) - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0) - add_compile_definitions(JSON_DLL_BUILD) - else() - add_definitions(-DJSON_DLL_BUILD) - endif() - set(SHARED_LIB ${PROJECT_NAME}_lib) add_library(${SHARED_LIB} SHARED ${PUBLIC_HEADERS} ${JSONCPP_SOURCES}) set_target_properties(${SHARED_LIB} PROPERTIES @@ -122,6 +116,8 @@ if(BUILD_SHARED_LIBS) POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS} ) + target_compile_definitions(${SHARED_LIB} PRIVATE JSON_DLL_BUILD) + # Set library's runtime search path on OSX if(APPLE) set_target_properties(${SHARED_LIB} PROPERTIES INSTALL_RPATH "@loader_path/.") From 8661f9e5fb51ae6bd839c635ba325f0fe7ba4974 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Sun, 15 Mar 2026 22:33:54 -0700 Subject: [PATCH 39/56] Fix number parsing failing under non-C locales (#1662) IStringStream inherits the global locale, so parsing doubles with operator>> fails when the locale uses ',' as the decimal separator (e.g. de_DE). Imbue the stream with std::locale::classic() in both Reader::decodeDouble and OurReader::decodeDouble. The writer already handles this correctly via fixNumericLocale(). Fixes #1565 --- RELEASE_1.9.7.md | 52 ++++++++++++++++++++++++++++++++++++ src/lib_json/json_reader.cpp | 2 ++ 2 files changed, 54 insertions(+) create mode 100644 RELEASE_1.9.7.md diff --git a/RELEASE_1.9.7.md b/RELEASE_1.9.7.md new file mode 100644 index 000000000..3607998d7 --- /dev/null +++ b/RELEASE_1.9.7.md @@ -0,0 +1,52 @@ +# jsoncpp 1.9.7 Release Work + +Issues to fix before tagging 1.9.7, each in a separate CL. + +--- + +## Done + +- [x] **#1656** — Fix uninitialized CMake variable `JSONCPP_VERSION` in `version.in` + → Change `@JSONCPP_VERSION@` to `@jsoncpp_VERSION@` + +--- + +## To Do + +### Security / Memory Safety + +- [ ] **#1626** — MemorySanitizer: use-of-uninitialized-value in `Json::Value::resolveReference` + → Uninitialized value detected by MSan in `json_value.cpp`. Need to identify and zero-initialize the offending member. + +- [ ] **#1623** — Use-after-free: `Json::Reader::parse` stores raw pointers into input string + → `Reader` stores `begin_`/`end_` pointers that dangle after the input `std::string` goes out of scope. `getFormattedErrorMessages()` then reads freed memory. + → Fix: copy the input document internally, or clearly document the lifetime requirement (the simpler option given the old Reader API is deprecated). + +### Correctness + +- [x] **#1565** — Number parsing breaks when user sets a non-C locale (e.g. `de_DE`) + → `istringstream`/`ostringstream` used for number parsing/writing inherit the global locale, which may use `,` as decimal separator instead of `.`. + → Fix: imbue streams with `std::locale::classic()` in `json_reader.cpp` and `json_writer.cpp`. + +- [ ] **#1546** — Control characters below 0x20 not rejected during parsing + → JSON spec requires rejecting unescaped control characters. jsoncpp currently accepts them. + +### Build / CMake + +- [ ] **#1634** — `JSON_DLL_BUILD` compile definition applied globally instead of per-target + → `add_compile_definitions` scopes it to all targets; should use `target_compile_definitions` scoped to the shared lib only. + +- [x] **#1598** — CMake 3.31 deprecation warning about compatibility with CMake < 3.10 + → Update `cmake_minimum_required` to use `...` version range syntax, e.g. `cmake_minimum_required(VERSION 3.10...3.31)`. + +- [x] **#1595** — Linker errors with `string_view` API when jsoncpp built as C++11 but consumer uses C++17 + → Root cause: `JSONCPP_HAS_STRING_VIEW` is not defined when building the library (forced C++11), but consumer with C++17 sees the `string_view` overloads in headers and tries to link them. + → Fix options: (a) export `JSONCPP_HAS_STRING_VIEW` in the CMake config so consumers see the same value, or (b) drop `CMAKE_CXX_STANDARD` force and use `target_compile_features(cxx_std_11)` instead. + +--- + +## Skipped (not bugs) + +- **#1548** — "Memory leak" after parsing large files: confirmed to be normal allocator behavior (OS doesn't immediately reclaim heap). Not a library bug. +- **#1533** — `clear()` then adding values fails: `clear()` preserves the value type by design. Confirmed user error. +- **#1547** — Trailing commas/garbage not rejected: existing behavior, controllable via `strictMode()`. Not a regression. diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 0697132b0..b0b6eb02f 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -583,6 +583,7 @@ bool Reader::decodeDouble(Token& token) { bool Reader::decodeDouble(Token& token, Value& decoded) { double value = 0; IStringStream is(String(token.start_, token.end_)); + is.imbue(std::locale::classic()); if (!(is >> value)) { if (value == std::numeric_limits::max()) value = std::numeric_limits::infinity(); @@ -1617,6 +1618,7 @@ bool OurReader::decodeDouble(Token& token) { bool OurReader::decodeDouble(Token& token, Value& decoded) { double value = 0; IStringStream is(String(token.start_, token.end_)); + is.imbue(std::locale::classic()); if (!(is >> value)) { if (value == std::numeric_limits::max()) value = std::numeric_limits::infinity(); From 94aee4f71552afcb8968ca8c7c1631471afa7ad8 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Sun, 15 Mar 2026 22:36:14 -0700 Subject: [PATCH 40/56] Reject unescaped control characters in JSON strings (#1663) RFC 8259 requires that control characters (U+0000-U+001F) be escaped when they appear inside strings. jsoncpp previously accepted them silently. Add a check in Reader::decodeString and OurReader::decodeString to return an error when an unescaped control character is encountered. Fixes #1546 --- src/lib_json/json_reader.cpp | 4 ++++ test/data/fail_test_control_char_01.json | 1 + 2 files changed, 5 insertions(+) create mode 100644 test/data/fail_test_control_char_01.json diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index b0b6eb02f..3faa2028f 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -655,6 +655,8 @@ bool Reader::decodeString(Token& token, String& decoded) { return addError("Bad escape sequence in string", token, current); } } else { + if (static_cast(c) < 0x20) + return addError("Control character in string", token, current - 1); decoded += c; } } @@ -1690,6 +1692,8 @@ bool OurReader::decodeString(Token& token, String& decoded) { return addError("Bad escape sequence in string", token, current); } } else { + if (static_cast(c) < 0x20) + return addError("Control character in string", token, current - 1); decoded += c; } } diff --git a/test/data/fail_test_control_char_01.json b/test/data/fail_test_control_char_01.json new file mode 100644 index 000000000..1f6835573 --- /dev/null +++ b/test/data/fail_test_control_char_01.json @@ -0,0 +1 @@ +"" \ No newline at end of file From d4742c2b4550d4ee516f065c87315c756ac2430f Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Sun, 15 Mar 2026 22:36:29 -0700 Subject: [PATCH 41/56] Fix MSAN issue in #1626 (#1654) * Fix MSAN issue in #1626 This patch fixes an MSAN issue by changing CZString initialization. * add test * expand tests * fix build * Export CZString with JSON_API to fix Windows DLL linker errors --- include/json/value.h | 6 +++- src/lib_json/json_value.cpp | 47 ++++++++++++++++++++---------- src/test_lib_json/main.cpp | 58 +++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 16 deletions(-) diff --git a/include/json/value.h b/include/json/value.h index 5f6544329..a7a39a1a7 100644 --- a/include/json/value.h +++ b/include/json/value.h @@ -50,6 +50,9 @@ #include #include +// Forward declaration for testing. +struct ValueTest; + #ifdef JSONCPP_HAS_STRING_VIEW #include #endif @@ -201,6 +204,7 @@ class JSON_API StaticString { */ class JSON_API Value { friend class ValueIteratorBase; + friend struct ::ValueTest; public: using Members = std::vector; @@ -266,7 +270,7 @@ class JSON_API Value { private: #endif #ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - class CZString { + class JSON_API CZString { public: enum DuplicationPolicy { noDuplication = 0, duplicate, duplicateOnCopy }; CZString(ArrayIndex index); diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp index a875d28b2..74f77896f 100644 --- a/src/lib_json/json_value.cpp +++ b/src/lib_json/json_value.cpp @@ -253,20 +253,29 @@ Value::CZString::CZString(const CZString& other) { cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != nullptr ? duplicateStringValue(other.cstr_, other.storage_.length_) : other.cstr_); - storage_.policy_ = - static_cast( - other.cstr_ - ? (static_cast(other.storage_.policy_) == - noDuplication - ? noDuplication - : duplicate) - : static_cast(other.storage_.policy_)) & - 3U; - storage_.length_ = other.storage_.length_; -} - -Value::CZString::CZString(CZString&& other) noexcept - : cstr_(other.cstr_), index_(other.index_) { + if (other.cstr_) { + storage_.policy_ = + static_cast( + other.cstr_ + ? (static_cast(other.storage_.policy_) == + noDuplication + ? noDuplication + : duplicate) + : static_cast(other.storage_.policy_)) & + 3U; + storage_.length_ = other.storage_.length_; + } else { + index_ = other.index_; + } +} + +Value::CZString::CZString(CZString&& other) noexcept : cstr_(other.cstr_) { + if (other.cstr_) { + storage_.policy_ = other.storage_.policy_; + storage_.length_ = other.storage_.length_; + } else { + index_ = other.index_; + } other.cstr_ = nullptr; } @@ -292,8 +301,16 @@ Value::CZString& Value::CZString::operator=(const CZString& other) { } Value::CZString& Value::CZString::operator=(CZString&& other) noexcept { + if (cstr_ && storage_.policy_ == duplicate) { + releasePrefixedStringValue(const_cast(cstr_)); + } cstr_ = other.cstr_; - index_ = other.index_; + if (other.cstr_) { + storage_.policy_ = other.storage_.policy_; + storage_.length_ = other.storage_.length_; + } else { + index_ = other.index_; + } other.cstr_ = nullptr; return *this; } diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index f19ca2fb4..d6938c1d0 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -150,6 +150,8 @@ struct ValueTest : JsonTest::TestCase { /// Normalize the representation of floating-point number by stripped leading /// 0 in exponent. static Json::String normalizeFloatingPointStr(const Json::String& s); + + void runCZStringTests(); }; Json::String ValueTest::normalizeFloatingPointStr(const Json::String& s) { @@ -167,6 +169,44 @@ Json::String ValueTest::normalizeFloatingPointStr(const Json::String& s) { return normalized + exponent; } +void ValueTest::runCZStringTests() { + // 1. Copy Constructor (Index) + Json::Value::CZString idx1(123); + Json::Value::CZString idx2(idx1); + JSONTEST_ASSERT_EQUAL(idx2.index(), 123); + + // 2. Move Constructor (Index) + Json::Value::CZString idx3(std::move(idx1)); + JSONTEST_ASSERT_EQUAL(idx3.index(), 123); + + // 3. Move Assignment (Index) + Json::Value::CZString idx4(456); + idx4 = std::move(idx3); + JSONTEST_ASSERT_EQUAL(idx4.index(), 123); + + // 4. Copy Constructor (String) + Json::Value::CZString str1("param", 5, + Json::Value::CZString::duplicateOnCopy); + Json::Value::CZString str2((str1)); // copy makes it duplicate (owning) + JSONTEST_ASSERT_STRING_EQUAL(str2.data(), "param"); + + // 5. Move Constructor (String) + // Move from Owning string (str2) + Json::Value::CZString str3(std::move(str2)); + JSONTEST_ASSERT_STRING_EQUAL(str3.data(), "param"); + + // 6. Move Assignment (String) + Json::Value::CZString str4("other", 5, + Json::Value::CZString::duplicateOnCopy); + Json::Value::CZString str5((str4)); // owning "other" + // Move-assign owning "param" (str3) into owning "other" (str5) + // This verifies we don't leak "other" (if fixed) and correctly take "param" + str5 = std::move(str3); + JSONTEST_ASSERT_STRING_EQUAL(str5.data(), "param"); +} + +JSONTEST_FIXTURE_LOCAL(ValueTest, CZStringCoverage) { runCZStringTests(); } + JSONTEST_FIXTURE_LOCAL(ValueTest, checkNormalizeFloatingPointStr) { struct TestData { std::string in; @@ -449,6 +489,24 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, resizeArray) { } } +JSONTEST_FIXTURE_LOCAL(ValueTest, copyMoveArray) { + Json::Value array; + array.append("item1"); + array.append("item2"); + + // Test Copy Constructor (covers CZString(const CZString&) with index) + Json::Value copy(array); + JSONTEST_ASSERT_EQUAL(copy.size(), 2); + JSONTEST_ASSERT_EQUAL(Json::Value("item1"), copy[0]); + JSONTEST_ASSERT_EQUAL(Json::Value("item2"), copy[1]); + + // Test Move Constructor (covers CZString(CZString&&) with index) + Json::Value moved(std::move(copy)); + JSONTEST_ASSERT_EQUAL(moved.size(), 2); + JSONTEST_ASSERT_EQUAL(Json::Value("item1"), moved[0]); + JSONTEST_ASSERT_EQUAL(Json::Value("item2"), moved[1]); +} + JSONTEST_FIXTURE_LOCAL(ValueTest, resizePopulatesAllMissingElements) { Json::ArrayIndex n = 10; Json::Value v; From 9a5bbec0d63804de9cf028a9fbc69f8fa0dc901e Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Sun, 15 Mar 2026 22:38:17 -0700 Subject: [PATCH 42/56] Fix string_view ABI mismatch between library and consumers (#1661) The library was forced to build with CMAKE_CXX_STANDARD 11, so JSONCPP_HAS_STRING_VIEW was never defined at compile time. Consumers building with C++17 would see the string_view APIs in the header but fail to link them. Fix: - Remove the global CMAKE_CXX_STANDARD 11 override; the existing target_compile_features(cxx_std_11) already enforces the minimum. - Detect string_view support at configure time with check_cxx_source_compiles and export JSONCPP_HAS_STRING_VIEW as a PUBLIC compile definition on all library targets, so consumers always see the same value the library was built with. - Guard the __cplusplus fallback in value.h so it does not override the CMake-set define. Fixes #1595 --- CMakeLists.txt | 6 ------ include/json/value.h | 2 ++ src/lib_json/CMakeLists.txt | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cfd1a4e1c..bc8575fd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,12 +40,6 @@ foreach(pold "") # Currently Empty endif() endforeach() -# Build the library with C++11 standard support, independent from other including -# software which may use a different CXX_STANDARD or CMAKE_CXX_STANDARD. -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - # Ensure that CMAKE_BUILD_TYPE has a value specified for single configuration generators. if(NOT DEFINED CMAKE_BUILD_TYPE AND NOT DEFINED CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE Release CACHE STRING diff --git a/include/json/value.h b/include/json/value.h index a7a39a1a7..f32f45609 100644 --- a/include/json/value.h +++ b/include/json/value.h @@ -39,9 +39,11 @@ #endif #endif +#ifndef JSONCPP_HAS_STRING_VIEW #if __cplusplus >= 201703L #define JSONCPP_HAS_STRING_VIEW 1 #endif +#endif #include #include diff --git a/src/lib_json/CMakeLists.txt b/src/lib_json/CMakeLists.txt index 965894158..03e933552 100644 --- a/src/lib_json/CMakeLists.txt +++ b/src/lib_json/CMakeLists.txt @@ -7,6 +7,7 @@ include(CheckIncludeFileCXX) include(CheckTypeSize) include(CheckStructHasMember) include(CheckCXXSymbolExists) +include(CheckCXXSourceCompiles) check_include_file_cxx(clocale HAVE_CLOCALE) check_cxx_symbol_exists(localeconv clocale HAVE_LOCALECONV) @@ -25,6 +26,11 @@ if(NOT (HAVE_CLOCALE AND HAVE_LCONV_SIZE AND HAVE_DECIMAL_POINT AND HAVE_LOCALEC endif() endif() +check_cxx_source_compiles( + "#include + int main() { std::string_view sv; return 0; }" + JSONCPP_HAS_STRING_VIEW) + set(JSONCPP_INCLUDE_DIR ../../include) set(PUBLIC_HEADERS @@ -125,6 +131,10 @@ if(BUILD_SHARED_LIBS) target_compile_features(${SHARED_LIB} PUBLIC ${REQUIRED_FEATURES}) + if(JSONCPP_HAS_STRING_VIEW) + target_compile_definitions(${SHARED_LIB} PUBLIC JSONCPP_HAS_STRING_VIEW=1) + endif() + target_include_directories(${SHARED_LIB} PUBLIC $ $ @@ -158,6 +168,10 @@ if(BUILD_STATIC_LIBS) target_compile_features(${STATIC_LIB} PUBLIC ${REQUIRED_FEATURES}) + if(JSONCPP_HAS_STRING_VIEW) + target_compile_definitions(${STATIC_LIB} PUBLIC JSONCPP_HAS_STRING_VIEW=1) + endif() + target_include_directories(${STATIC_LIB} PUBLIC $ $ @@ -184,6 +198,10 @@ if(BUILD_OBJECT_LIBS) target_compile_features(${OBJECT_LIB} PUBLIC ${REQUIRED_FEATURES}) + if(JSONCPP_HAS_STRING_VIEW) + target_compile_definitions(${OBJECT_LIB} PUBLIC JSONCPP_HAS_STRING_VIEW=1) + endif() + target_include_directories(${OBJECT_LIB} PUBLIC $ $ From 02e903a847fb30366491007b45cf8e90ac871114 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Sun, 15 Mar 2026 22:45:14 -0700 Subject: [PATCH 43/56] Revert "Fix number parsing failing under non-C locales" (#1664) * Revert "Fix number parsing failing under non-C locales (#1662)" This reverts commit 8661f9e5fb51ae6bd839c635ba325f0fe7ba4974. * readd json reader changes --- RELEASE_1.9.7.md | 52 ------------------------------------------------ 1 file changed, 52 deletions(-) delete mode 100644 RELEASE_1.9.7.md diff --git a/RELEASE_1.9.7.md b/RELEASE_1.9.7.md deleted file mode 100644 index 3607998d7..000000000 --- a/RELEASE_1.9.7.md +++ /dev/null @@ -1,52 +0,0 @@ -# jsoncpp 1.9.7 Release Work - -Issues to fix before tagging 1.9.7, each in a separate CL. - ---- - -## Done - -- [x] **#1656** — Fix uninitialized CMake variable `JSONCPP_VERSION` in `version.in` - → Change `@JSONCPP_VERSION@` to `@jsoncpp_VERSION@` - ---- - -## To Do - -### Security / Memory Safety - -- [ ] **#1626** — MemorySanitizer: use-of-uninitialized-value in `Json::Value::resolveReference` - → Uninitialized value detected by MSan in `json_value.cpp`. Need to identify and zero-initialize the offending member. - -- [ ] **#1623** — Use-after-free: `Json::Reader::parse` stores raw pointers into input string - → `Reader` stores `begin_`/`end_` pointers that dangle after the input `std::string` goes out of scope. `getFormattedErrorMessages()` then reads freed memory. - → Fix: copy the input document internally, or clearly document the lifetime requirement (the simpler option given the old Reader API is deprecated). - -### Correctness - -- [x] **#1565** — Number parsing breaks when user sets a non-C locale (e.g. `de_DE`) - → `istringstream`/`ostringstream` used for number parsing/writing inherit the global locale, which may use `,` as decimal separator instead of `.`. - → Fix: imbue streams with `std::locale::classic()` in `json_reader.cpp` and `json_writer.cpp`. - -- [ ] **#1546** — Control characters below 0x20 not rejected during parsing - → JSON spec requires rejecting unescaped control characters. jsoncpp currently accepts them. - -### Build / CMake - -- [ ] **#1634** — `JSON_DLL_BUILD` compile definition applied globally instead of per-target - → `add_compile_definitions` scopes it to all targets; should use `target_compile_definitions` scoped to the shared lib only. - -- [x] **#1598** — CMake 3.31 deprecation warning about compatibility with CMake < 3.10 - → Update `cmake_minimum_required` to use `...` version range syntax, e.g. `cmake_minimum_required(VERSION 3.10...3.31)`. - -- [x] **#1595** — Linker errors with `string_view` API when jsoncpp built as C++11 but consumer uses C++17 - → Root cause: `JSONCPP_HAS_STRING_VIEW` is not defined when building the library (forced C++11), but consumer with C++17 sees the `string_view` overloads in headers and tries to link them. - → Fix options: (a) export `JSONCPP_HAS_STRING_VIEW` in the CMake config so consumers see the same value, or (b) drop `CMAKE_CXX_STANDARD` force and use `target_compile_features(cxx_std_11)` instead. - ---- - -## Skipped (not bugs) - -- **#1548** — "Memory leak" after parsing large files: confirmed to be normal allocator behavior (OS doesn't immediately reclaim heap). Not a library bug. -- **#1533** — `clear()` then adding values fails: `clear()` preserves the value type by design. Confirmed user error. -- **#1547** — Trailing commas/garbage not rejected: existing behavior, controllable via `strictMode()`. Not a regression. From ce757bee45b84a42885af06897e378247ccecb94 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Sun, 15 Mar 2026 22:51:09 -0700 Subject: [PATCH 44/56] Fix use-after-free in Reader::parse(std::istream&) (#1665) The istream overload stored the document in a local String then passed raw pointers into it to parse(const char*, const char*), which kept those pointers in begin_/end_. After parse() returned the local String was destroyed, leaving begin_/end_ dangling. Any subsequent call to getFormattedErrorMessages() would then read freed memory. Fix by reading the stream into the member document_ instead, matching the behavior of parse(const std::string&). Also document the lifetime requirement on parse(const char*, const char*): the caller's buffer must outlive the Reader if error-reporting methods are used after parsing. Fixes #1623 --- include/json/reader.h | 5 ++++- src/lib_json/json_reader.cpp | 13 ++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/include/json/reader.h b/include/json/reader.h index d745378fc..7aa227188 100644 --- a/include/json/reader.h +++ b/include/json/reader.h @@ -81,7 +81,10 @@ class JSON_API Reader { * document. * * \param beginDoc Pointer on the beginning of the UTF-8 encoded - * string of the document to read. + * string of the document to read. The pointed-to + * buffer must outlive this Reader if error + * methods (e.g. getFormattedErrorMessages()) are + * called after parse() returns. * \param endDoc Pointer on the end of the UTF-8 encoded string * of the document to read. Must be >= beginDoc. * \param[out] root Contains the root value of the document if it diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 3faa2028f..83743f73b 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -88,15 +88,10 @@ bool Reader::parse(const std::string& document, Value& root, } bool Reader::parse(std::istream& is, Value& root, bool collectComments) { - // std::istream_iterator begin(is); - // std::istream_iterator end; - // Those would allow streamed input from a file, if parse() were a - // template function. - - // Since String is reference-counted, this at least does not - // create an extra copy. - String doc(std::istreambuf_iterator(is), {}); - return parse(doc.data(), doc.data() + doc.size(), root, collectComments); + document_.assign(std::istreambuf_iterator(is), + std::istreambuf_iterator()); + return parse(document_.data(), document_.data() + document_.size(), root, + collectComments); } bool Reader::parse(const char* beginDoc, const char* endDoc, Value& root, From 3455302847cf1e4671f1d8f5fa953fd46a7b1404 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Mon, 16 Mar 2026 13:09:40 -0700 Subject: [PATCH 45/56] Update bazel config for 9.x (#1655) * Update bazel config for 9.x Bazel 9.x+ requires explicit load statements for things that were previously included in bazel. I automatically added these with `buildifier` and added some reasonable minimum versions to the MODULE.bazel file. * Fix tests for Bazel 9.x sandbox --------- Co-authored-by: Jordan Bayles Co-authored-by: Jordan Bayles --- BUILD.bazel | 7 ++++--- MODULE.bazel | 8 ++++++++ example/BUILD.bazel | 6 ++++-- src/jsontestrunner/BUILD.bazel | 4 +++- src/test_lib_json/BUILD.bazel | 8 +++++--- test/BUILD.bazel | 15 ++++++++++++--- 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/BUILD.bazel b/BUILD.bazel index 2227fee23..45f7a21b3 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,6 +1,7 @@ -licenses(["unencumbered"]) # Public Domain or MIT - load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +licenses(["unencumbered"]) # Public Domain or MIT exports_files(["LICENSE"]) @@ -36,9 +37,9 @@ cc_library( "include/json/allocator.h", "include/json/assertions.h", "include/json/config.h", - "include/json/json_features.h", "include/json/forwards.h", "include/json/json.h", + "include/json/json_features.h", "include/json/reader.h", "include/json/value.h", "include/json/version.h", diff --git a/MODULE.bazel b/MODULE.bazel index e60fa06d1..e29304fa6 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -17,3 +17,11 @@ bazel_dep( name = "bazel_skylib", version = "1.7.1", ) +bazel_dep( + name = "rules_cc", + version = "0.0.17", +) +bazel_dep( + name = "rules_python", + version = "1.0.0", +) diff --git a/example/BUILD.bazel b/example/BUILD.bazel index 38e7dfcf7..ebbd0b58e 100644 --- a/example/BUILD.bazel +++ b/example/BUILD.bazel @@ -1,17 +1,19 @@ +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") + cc_binary( name = "readFromStream_ok", srcs = ["readFromStream/readFromStream.cpp"], - deps = ["//:jsoncpp"], args = ["$(location :readFromStream/withComment.json)"], data = ["readFromStream/withComment.json"], + deps = ["//:jsoncpp"], ) cc_binary( name = "readFromStream_err", srcs = ["readFromStream/readFromStream.cpp"], - deps = ["//:jsoncpp"], args = ["$(location :readFromStream/errorFormat.json)"], data = ["readFromStream/errorFormat.json"], + deps = ["//:jsoncpp"], ) cc_binary( diff --git a/src/jsontestrunner/BUILD.bazel b/src/jsontestrunner/BUILD.bazel index 543bc5d88..fa1f39510 100644 --- a/src/jsontestrunner/BUILD.bazel +++ b/src/jsontestrunner/BUILD.bazel @@ -1,6 +1,8 @@ +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") + cc_binary( name = "jsontestrunner", srcs = ["main.cpp"], - deps = ["//:jsoncpp"], visibility = ["//test:__pkg__"], + deps = ["//:jsoncpp"], ) diff --git a/src/test_lib_json/BUILD.bazel b/src/test_lib_json/BUILD.bazel index 7e83f5219..18b2a1801 100644 --- a/src/test_lib_json/BUILD.bazel +++ b/src/test_lib_json/BUILD.bazel @@ -1,11 +1,13 @@ +load("@rules_cc//cc:cc_test.bzl", "cc_test") + cc_test( name = "jsoncpp_test", srcs = [ + "fuzz.cpp", + "fuzz.h", "jsontest.cpp", "jsontest.h", "main.cpp", - "fuzz.h", - "fuzz.cpp", - ], + ], deps = ["//:jsoncpp"], ) diff --git a/test/BUILD.bazel b/test/BUILD.bazel index 269cd8646..c1a3623a1 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel @@ -1,20 +1,29 @@ +load("@rules_python//python:defs.bzl", "py_test") + filegroup( name = "expected", - srcs = glob(["data/**", "jsonchecker/**"], exclude=["**/*.json"]), + srcs = glob( + [ + "data/**", + "jsonchecker/**", + ], + exclude = ["**/*.json"], + ), ) [py_test( name = "runjson_%s_test" % "_".join(f.split("/")), srcs = ["runjsontests.py"], - main = "runjsontests.py", args = [ "--with-json-checker", "$(location //src/jsontestrunner:jsontestrunner)", "$(location :%s)" % f, ], data = [ - "//src/jsontestrunner:jsontestrunner", ":expected", + "//src/jsontestrunner", ":%s" % f, ], + main = "runjsontests.py", + tags = ["no-sandbox"], ) for f in glob(["**/*.json"])] From 8e9b4e462bdd84e932a095190fead8ec39654f53 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Thu, 19 Mar 2026 17:38:44 -0700 Subject: [PATCH 46/56] Update version to 1.9.8, remove qualifier from version string (#1666) * improve qualifier support * move to va args * remove qualifier and focus on strict Semantic Versioning --- .github/workflows/update-project-version.yml | 13 ++----------- CMakeLists.txt | 4 ++-- MODULE.bazel | 2 +- include/json/version.h | 5 ++--- meson.build | 4 ++-- 5 files changed, 9 insertions(+), 19 deletions(-) diff --git a/.github/workflows/update-project-version.yml b/.github/workflows/update-project-version.yml index af03a5566..c00363837 100644 --- a/.github/workflows/update-project-version.yml +++ b/.github/workflows/update-project-version.yml @@ -4,9 +4,9 @@ on: workflow_dispatch: inputs: target_version: - description: 'next version (e.g., 1.9.7 or 1.9.7.12)' + description: 'next version (e.g., 1.9.8)' required: true - default: '1.9.7' + default: '1.9.8' target_soversion: description: 'next soversion (e.g., 28). leave blank to keep current.' required: false @@ -60,18 +60,11 @@ jobs: 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 - else - sed -i "s/#define JSONCPP_VERSION_QUALIFIER.*/#define JSONCPP_VERSION_QUALIFIER/" include/json/version.h - fi fi - name: verify version macros @@ -84,7 +77,6 @@ jobs: 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) @@ -95,7 +87,6 @@ jobs: 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 diff --git a/CMakeLists.txt b/CMakeLists.txt index bc8575fd1..0cc34c98a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,11 +57,11 @@ project(jsoncpp # 3. ./CMakeLists.txt # 4. ./MODULE.bazel # IMPORTANT: also update the PROJECT_SOVERSION!! - VERSION 1.9.7 # [.[.[.]]] + VERSION 1.9.8 # [.[.[.]]] LANGUAGES CXX) message(STATUS "JsonCpp Version: ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") -set(PROJECT_SOVERSION 27) +set(PROJECT_SOVERSION 28) include(${CMAKE_CURRENT_SOURCE_DIR}/include/PreventInSourceBuilds.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/include/PreventInBuildInstalls.cmake) diff --git a/MODULE.bazel b/MODULE.bazel index e29304fa6..25b8bfc8c 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.7", + version = "1.9.8", compatibility_level = 1, ) diff --git a/include/json/version.h b/include/json/version.h index 555152c8c..1579c7807 100644 --- a/include/json/version.h +++ b/include/json/version.h @@ -10,11 +10,10 @@ // 4. /MODULE.bazel // IMPORTANT: also update the SOVERSION!! -#define JSONCPP_VERSION_STRING "1.9.7" +#define JSONCPP_VERSION_STRING "1.9.8" #define JSONCPP_VERSION_MAJOR 1 #define JSONCPP_VERSION_MINOR 9 -#define JSONCPP_VERSION_PATCH 7 -#define JSONCPP_VERSION_QUALIFIER +#define JSONCPP_VERSION_PATCH 8 #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 2648c3071..efcbaa43b 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.7', + version : '1.9.8', default_options : [ 'buildtype=release', 'cpp_std=c++11', @@ -51,7 +51,7 @@ jsoncpp_lib = library( 'src/lib_json/json_value.cpp', 'src/lib_json/json_writer.cpp', ]), - soversion : 27, + soversion : 28, install : true, include_directories : jsoncpp_include_directories, cpp_args: dll_export_flag) From cdc84831f1861c4372cc2ced8284f0b5d0bcec5a Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Thu, 19 Mar 2026 17:59:27 -0700 Subject: [PATCH 47/56] Revert soversion change -- unnecessary (#1667) --- CMakeLists.txt | 2 +- meson.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0cc34c98a..c1de7aefc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,7 @@ project(jsoncpp LANGUAGES CXX) message(STATUS "JsonCpp Version: ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") -set(PROJECT_SOVERSION 28) +set(PROJECT_SOVERSION 27) include(${CMAKE_CURRENT_SOURCE_DIR}/include/PreventInSourceBuilds.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/include/PreventInBuildInstalls.cmake) diff --git a/meson.build b/meson.build index efcbaa43b..e08314bcd 100644 --- a/meson.build +++ b/meson.build @@ -51,7 +51,7 @@ jsoncpp_lib = library( 'src/lib_json/json_value.cpp', 'src/lib_json/json_writer.cpp', ]), - soversion : 28, + soversion : 27, install : true, include_directories : jsoncpp_include_directories, cpp_args: dll_export_flag) From 19a794d79a5d6074e7d573e88e705b4543775edf Mon Sep 17 00:00:00 2001 From: Jie Luo Date: Tue, 7 Apr 2026 19:54:04 -0700 Subject: [PATCH 48/56] prevent macro redefined for JSON_HAS_INT64 (#1673) --- include/json/config.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/json/config.h b/include/json/config.h index 7f6e2431b..6971fa656 100644 --- a/include/json/config.h +++ b/include/json/config.h @@ -122,7 +122,9 @@ using UInt64 = uint64_t; #endif // if defined(_MSC_VER) using LargestInt = Int64; using LargestUInt = UInt64; +#ifndef JSON_HAS_INT64 #define JSON_HAS_INT64 +#endif // ifndef JSON_HAS_INT64 #endif // if defined(JSON_NO_INT64) template From 87576c45e62c2365a1238d15057bd3e8a22173f7 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Tue, 7 Apr 2026 20:29:23 -0700 Subject: [PATCH 49/56] Fix CMake 4.0 compatibility in jsoncppConfig.cmake.in (#1671) CMake 4.0 has removed compatibility with policy versions below 3.5. This change updates the minimum policy version from 3.0 to 3.5 in `jsoncppConfig.cmake.in` to prevent a fatal configuration error when downstream projects use `find_package(jsoncpp)` with CMake 4.0+. --- jsoncppConfig.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsoncppConfig.cmake.in b/jsoncppConfig.cmake.in index fdd9fea6b..35fed3687 100644 --- a/jsoncppConfig.cmake.in +++ b/jsoncppConfig.cmake.in @@ -1,5 +1,5 @@ cmake_policy(PUSH) -cmake_policy(VERSION 3.0...3.26) +cmake_policy(VERSION 3.5...3.26) @PACKAGE_INIT@ From 2c2754f3c935bfb86af21b8b1636b16a98e793f6 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Tue, 7 Apr 2026 20:46:24 -0700 Subject: [PATCH 50/56] ci: suppress Node 20 deprecation and missing python-version warnings (#1674) - Injects `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true` in all workflows to opt-in to Node.js 24 and suppress the deprecation warnings from multiple GitHub Actions. - Specifies `python-version: '3.x'` for `actions/setup-python@v5` in `meson.yml` to fix the missing input warning. --- .github/workflows/clang-format.yml | 3 +++ .github/workflows/cmake.yml | 4 ++++ .github/workflows/meson.yml | 7 +++++++ .github/workflows/update-project-version.yml | 3 +++ 4 files changed, 17 insertions(+) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 221f8b839..eca3c31f5 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -1,6 +1,9 @@ name: clang-format check on: [check_run, pull_request, push] +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: formatting-check: name: formatting check diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 91f387a50..55452ac25 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -1,5 +1,9 @@ name: cmake on: [check_run, push, pull_request] + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: cmake-publish: runs-on: ${{ matrix.os }} diff --git a/.github/workflows/meson.yml b/.github/workflows/meson.yml index 22fe32f72..92d04862f 100644 --- a/.github/workflows/meson.yml +++ b/.github/workflows/meson.yml @@ -2,6 +2,9 @@ name: meson build and test run-name: update pushed to ${{ github.ref }} on: [check_run, push, pull_request] +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: meson-publish: runs-on: ${{ matrix.os }} @@ -17,6 +20,8 @@ jobs: - name: setup python uses: actions/setup-python@v5 + with: + python-version: '3.x' - name: meson build uses: BSFishy/meson-build@v1.0.3 @@ -41,6 +46,8 @@ jobs: - name: setup python uses: actions/setup-python@v5 + with: + python-version: '3.x' - name: meson build uses: BSFishy/meson-build@v1.0.3 diff --git a/.github/workflows/update-project-version.yml b/.github/workflows/update-project-version.yml index c00363837..c47e53790 100644 --- a/.github/workflows/update-project-version.yml +++ b/.github/workflows/update-project-version.yml @@ -11,6 +11,9 @@ on: description: 'next soversion (e.g., 28). leave blank to keep current.' required: false +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: bump-and-verify: runs-on: ubuntu-latest From c67034e4b4c722579ee15fddb8e4af8f04252b08 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Thu, 9 Apr 2026 10:37:08 -0700 Subject: [PATCH 51/56] Fix C++11 ABI breakage when compiled with C++17 #1668 (#1675) * ci: suppress Node 20 deprecation and missing python-version warnings - Injects `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true` in all workflows to opt-in to Node.js 24 and suppress the deprecation warnings from multiple GitHub Actions. - Specifies `python-version: '3.x'` for `actions/setup-python@v5` in `meson.yml` to fix the missing input warning. * Fix C++11 ABI breakage when compiled with C++17 (#1668) When JSONCPP_HAS_STRING_VIEW was defined, the library dropped the `const char*` and `const String&` overloads for `operator[]`, `get`, `removeMember`, and `isMember`, breaking ABI compatibility for projects consuming the library with C++11. This change unconditionally declares and defines the legacy overloads so they are always exported, restoring compatibility. * ci: add ABI compatibility matrix workflow This adds a new GitHub Actions workflow to verify ABI compatibility across C++ standard boundaries. It explicitly tests the scenario where JsonCpp is built with one standard (e.g., C++11) and consumed by an application built with a newer one (e.g., C++23), and vice versa. To facilitate testing the specific `std::string_view` boundary that is conditionally compiled, a new `stringView` demo application has been added to the `example/` directory and is consumed directly by the CI matrix to ensure standard library symbols link correctly across standard versions, build types (shared/static), and operating systems. * fix: inline std::string_view methods to prevent ABI breaks This commit completely eliminates the ABI breakage that occurs across C++ standard boundaries when using `std::string_view`. Previously, when the library was built with C++17+, CMake would leak `JSONCPP_HAS_STRING_VIEW=1` as a PUBLIC definition. A C++11 consumer would receive this definition, attempt to parse the header, and fail with compiler errors because `std::string_view` is not available in their environment. Conversely, if the library was built in C++11 (without `string_view` symbols), a C++17 consumer would naturally define `JSONCPP_HAS_STRING_VIEW` based on `__cplusplus` inside `value.h`. The consumer would then call the declared `string_view` methods, resulting in linker errors because the methods weren't compiled into the library. By moving all `std::string_view` overloads directly into `value.h` as `inline` methods that delegate to the fundamental `const char*, const char*` methods: 1. The consumer's compiler dictates whether the overloads are visible (via `__cplusplus >= 201703L`). 2. The consumer compiles the inline wrappers locally, removing any reliance on the library's exported symbols for `std::string_view`. 3. CMake no longer needs to pollute the consumer's environment with PUBLIC compile definitions. * run clang format * finish clang format --- .github/workflows/abi-compatibility.yml | 79 +++++++++++++++++++++++++ example/BUILD.bazel | 6 ++ example/CMakeLists.txt | 1 + example/stringView/stringView.cpp | 29 +++++++++ include/json/value.h | 54 +++++++++++------ src/lib_json/CMakeLists.txt | 9 --- src/lib_json/json_value.cpp | 66 --------------------- 7 files changed, 151 insertions(+), 93 deletions(-) create mode 100644 .github/workflows/abi-compatibility.yml create mode 100644 example/stringView/stringView.cpp diff --git a/.github/workflows/abi-compatibility.yml b/.github/workflows/abi-compatibility.yml new file mode 100644 index 000000000..881c4e8e0 --- /dev/null +++ b/.github/workflows/abi-compatibility.yml @@ -0,0 +1,79 @@ +name: ABI Compatibility + +on: [check_run, push, pull_request] + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + abi-compatibility: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + shared_libs: [ON, OFF] + include: + - jsoncpp_std: 11 + app_std: 23 + - jsoncpp_std: 23 + app_std: 11 + + steps: + - name: checkout project + uses: actions/checkout@v4 + + - name: build and install JsonCpp (C++${{ matrix.jsoncpp_std }}) + shell: bash + run: | + mkdir build-jsoncpp + cd build-jsoncpp + cmake .. -DCMAKE_CXX_STANDARD=${{ matrix.jsoncpp_std }} \ + -DCMAKE_CXX_STANDARD_REQUIRED=ON \ + -DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-jsoncpp \ + -DBUILD_SHARED_LIBS=${{ matrix.shared_libs }} \ + -DJSONCPP_WITH_TESTS=OFF + cmake --build . --config Release + cmake --install . --config Release + + - name: create example app + shell: bash + run: | + mkdir example-app + cat << 'EOF' > example-app/CMakeLists.txt + cmake_minimum_required(VERSION 3.10) + project(abi_test) + + find_package(jsoncpp REQUIRED CONFIG) + + add_executable(abi_test stringView.cpp) + target_link_libraries(abi_test PRIVATE JsonCpp::JsonCpp) + EOF + + cp $GITHUB_WORKSPACE/example/stringView/stringView.cpp example-app/stringView.cpp + + - name: build example app (C++${{ matrix.app_std }}) + shell: bash + run: | + cd example-app + mkdir build + cd build + cmake .. -DCMAKE_CXX_STANDARD=${{ matrix.app_std }} \ + -DCMAKE_CXX_STANDARD_REQUIRED=ON \ + -DCMAKE_PREFIX_PATH=$GITHUB_WORKSPACE/install-jsoncpp + cmake --build . --config Release + + - name: run example app + shell: bash + run: | + if [ "$RUNNER_OS" == "Windows" ]; then + export PATH=$GITHUB_WORKSPACE/install-jsoncpp/bin:$PATH + ./example-app/build/Release/abi_test.exe + elif [ "$RUNNER_OS" == "macOS" ]; then + export DYLD_LIBRARY_PATH=$GITHUB_WORKSPACE/install-jsoncpp/lib:$DYLD_LIBRARY_PATH + ./example-app/build/abi_test + else + export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/install-jsoncpp/lib:$LD_LIBRARY_PATH + ./example-app/build/abi_test + fi diff --git a/example/BUILD.bazel b/example/BUILD.bazel index ebbd0b58e..35813085b 100644 --- a/example/BUILD.bazel +++ b/example/BUILD.bazel @@ -33,3 +33,9 @@ cc_binary( srcs = ["stringWrite/stringWrite.cpp"], deps = ["//:jsoncpp"], ) + +cc_binary( + name = "stringView", + srcs = ["stringView/stringView.cpp"], + deps = ["//:jsoncpp"], +) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 230d1bd7b..0666db763 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -4,6 +4,7 @@ set(EXAMPLES readFromStream stringWrite streamWrite + stringView ) add_definitions(-D_GLIBCXX_USE_CXX11_ABI) diff --git a/example/stringView/stringView.cpp b/example/stringView/stringView.cpp new file mode 100644 index 000000000..b5e33e9fd --- /dev/null +++ b/example/stringView/stringView.cpp @@ -0,0 +1,29 @@ +#include "json/json.h" +#include +#include + +#if defined(JSONCPP_HAS_STRING_VIEW) +#include +#endif + +/** + * \brief Example using std::string_view with JsonCpp. + */ +int main() { + Json::Value root; + root["key"] = "value"; + +#if defined(JSONCPP_HAS_STRING_VIEW) + std::cout << "Has string_view support" << std::endl; + std::string_view sv("key"); + if (root.isMember(sv)) { + std::cout << root[sv].asString() << std::endl; + } +#else + std::cout << "No string_view support" << std::endl; + if (root.isMember("key")) { + std::cout << root["key"].asString() << std::endl; + } +#endif + return EXIT_SUCCESS; +} diff --git a/include/json/value.h b/include/json/value.h index f32f45609..2007e6b42 100644 --- a/include/json/value.h +++ b/include/json/value.h @@ -357,7 +357,8 @@ class JSON_API Value { Value(const StaticString& value); Value(const String& value); #ifdef JSONCPP_HAS_STRING_VIEW - Value(std::string_view value); + inline Value(std::string_view value) + : Value(value.data(), value.data() + value.length()) {} #endif Value(bool value); Value(std::nullptr_t ptr) = delete; @@ -405,7 +406,14 @@ class JSON_API Value { /** Get string_view of string-value. * \return false if !string. (Seg-fault if str is NULL.) */ - bool getString(std::string_view* str) const; + inline bool getString(std::string_view* str) const { + char const* begin; + char const* end; + if (!getString(&begin, &end)) + return false; + *str = std::string_view(begin, static_cast(end - begin)); + return true; + } #endif Int asInt() const; UInt asUInt() const; @@ -496,12 +504,19 @@ class JSON_API Value { #ifdef JSONCPP_HAS_STRING_VIEW /// Access an object value by name, create a null member if it does not exist. /// \param key may contain embedded nulls. - Value& operator[](std::string_view key); + inline Value& operator[](std::string_view key) { + return resolveReference(key.data(), key.data() + key.length()); + } /// Access an object value by name, returns null if there is no member with /// that name. /// \param key may contain embedded nulls. - const Value& operator[](std::string_view key) const; -#else + inline const Value& operator[](std::string_view key) const { + Value const* found = find(key.data(), key.data() + key.length()); + if (!found) + return nullSingleton(); + return *found; + } +#endif /// Access an object value by name, create a null member if it does not exist. /// \note Because of our implementation, keys are limited to 2^30 -1 chars. /// Exceeding that will cause an exception. @@ -516,7 +531,6 @@ class JSON_API Value { /// that name. /// \param key may contain embedded nulls. const Value& operator[](const String& key) const; -#endif /** \brief Access an object value by name, create a null member if it does not * exist. * @@ -533,8 +547,10 @@ class JSON_API Value { #ifdef JSONCPP_HAS_STRING_VIEW /// Return the member named key if it exist, defaultValue otherwise. /// \note deep copy - Value get(std::string_view key, const Value& defaultValue) const; -#else + inline Value get(std::string_view key, const Value& defaultValue) const { + return get(key.data(), key.data() + key.length(), defaultValue); + } +#endif /// Return the member named key if it exist, defaultValue otherwise. /// \note deep copy Value get(const char* key, const Value& defaultValue) const; @@ -542,7 +558,6 @@ class JSON_API Value { /// \note deep copy /// \param key may contain embedded nulls. Value get(const String& key, const Value& defaultValue) const; -#endif /// Return the member named key if it exist, defaultValue otherwise. /// \note deep copy /// \note key may contain embedded nulls. @@ -588,13 +603,14 @@ class JSON_API Value { /// \pre type() is objectValue or nullValue /// \post type() is unchanged #if JSONCPP_HAS_STRING_VIEW - void removeMember(std::string_view key); -#else + inline void removeMember(std::string_view key) { + removeMember(key.data(), key.data() + key.length(), nullptr); + } +#endif void removeMember(const char* key); /// Same as removeMember(const char*) /// \param key may contain embedded nulls. void removeMember(const String& key); -#endif /** \brief Remove the named map member. * * Update 'removed' iff removed. @@ -602,13 +618,14 @@ class JSON_API Value { * \return true iff removed (no exceptions) */ #if JSONCPP_HAS_STRING_VIEW - bool removeMember(std::string_view key, Value* removed); -#else + inline bool removeMember(std::string_view key, Value* removed) { + return removeMember(key.data(), key.data() + key.length(), removed); + } +#endif bool removeMember(String const& key, Value* removed); /// Same as removeMember(const char* begin, const char* end, Value* removed), /// but 'key' is null-terminated. bool removeMember(const char* key, Value* removed); -#endif /// Same as removeMember(String const& key, Value* removed) bool removeMember(const char* begin, const char* end, Value* removed); /** \brief Remove the indexed array element. @@ -622,15 +639,16 @@ class JSON_API Value { #ifdef JSONCPP_HAS_STRING_VIEW /// Return true if the object has a member named key. /// \param key may contain embedded nulls. - bool isMember(std::string_view key) const; -#else + inline bool isMember(std::string_view key) const { + return isMember(key.data(), key.data() + key.length()); + } +#endif /// Return true if the object has a member named key. /// \note 'key' must be null-terminated. bool isMember(const char* key) const; /// Return true if the object has a member named key. /// \param key may contain embedded nulls. bool isMember(const String& key) const; -#endif /// Same as isMember(String const& key)const bool isMember(const char* begin, const char* end) const; diff --git a/src/lib_json/CMakeLists.txt b/src/lib_json/CMakeLists.txt index 03e933552..a0695e9eb 100644 --- a/src/lib_json/CMakeLists.txt +++ b/src/lib_json/CMakeLists.txt @@ -131,9 +131,6 @@ if(BUILD_SHARED_LIBS) target_compile_features(${SHARED_LIB} PUBLIC ${REQUIRED_FEATURES}) - if(JSONCPP_HAS_STRING_VIEW) - target_compile_definitions(${SHARED_LIB} PUBLIC JSONCPP_HAS_STRING_VIEW=1) - endif() target_include_directories(${SHARED_LIB} PUBLIC $ @@ -168,9 +165,6 @@ if(BUILD_STATIC_LIBS) target_compile_features(${STATIC_LIB} PUBLIC ${REQUIRED_FEATURES}) - if(JSONCPP_HAS_STRING_VIEW) - target_compile_definitions(${STATIC_LIB} PUBLIC JSONCPP_HAS_STRING_VIEW=1) - endif() target_include_directories(${STATIC_LIB} PUBLIC $ @@ -198,9 +192,6 @@ if(BUILD_OBJECT_LIBS) target_compile_features(${OBJECT_LIB} PUBLIC ${REQUIRED_FEATURES}) - if(JSONCPP_HAS_STRING_VIEW) - target_compile_definitions(${OBJECT_LIB} PUBLIC JSONCPP_HAS_STRING_VIEW=1) - endif() target_include_directories(${OBJECT_LIB} PUBLIC $ diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp index 74f77896f..a8eb72d6b 100644 --- a/src/lib_json/json_value.cpp +++ b/src/lib_json/json_value.cpp @@ -441,14 +441,6 @@ Value::Value(const String& value) { value.data(), static_cast(value.length())); } -#ifdef JSONCPP_HAS_STRING_VIEW -Value::Value(std::string_view value) { - initBasic(stringValue, true); - value_.string_ = duplicateAndPrefixStringValue( - value.data(), static_cast(value.length())); -} -#endif - Value::Value(const StaticString& value) { initBasic(stringValue); value_.string_ = const_cast(value.c_str()); @@ -656,21 +648,6 @@ bool Value::getString(char const** begin, char const** end) const { return true; } -#ifdef JSONCPP_HAS_STRING_VIEW -bool Value::getString(std::string_view* str) const { - if (type() != stringValue) - return false; - if (value_.string_ == nullptr) - return false; - const char* begin; - unsigned length; - decodePrefixedString(this->isAllocated(), this->value_.string_, &length, - &begin); - *str = std::string_view(begin, length); - return true; -} -#endif - String Value::asString() const { switch (type()) { case nullValue: @@ -1190,17 +1167,6 @@ Value* Value::demand(char const* begin, char const* end) { "objectValue or nullValue"); return &resolveReference(begin, end); } -#ifdef JSONCPP_HAS_STRING_VIEW -const Value& Value::operator[](std::string_view key) const { - Value const* found = find(key.data(), key.data() + key.length()); - if (!found) - return nullSingleton(); - return *found; -} -Value& Value::operator[](std::string_view key) { - return resolveReference(key.data(), key.data() + key.length()); -} -#else const Value& Value::operator[](const char* key) const { Value const* found = find(key, key + strlen(key)); if (!found) @@ -1221,7 +1187,6 @@ Value& Value::operator[](const char* key) { Value& Value::operator[](const String& key) { return resolveReference(key.data(), key.data() + key.length()); } -#endif Value& Value::operator[](const StaticString& key) { return resolveReference(key.c_str()); @@ -1261,18 +1226,12 @@ Value Value::get(char const* begin, char const* end, Value const* found = find(begin, end); return !found ? defaultValue : *found; } -#ifdef JSONCPP_HAS_STRING_VIEW -Value Value::get(std::string_view key, const Value& defaultValue) const { - return get(key.data(), key.data() + key.length(), defaultValue); -} -#else Value Value::get(char const* key, Value const& defaultValue) const { return get(key, key + strlen(key), defaultValue); } Value Value::get(String const& key, Value const& defaultValue) const { return get(key.data(), key.data() + key.length(), defaultValue); } -#endif bool Value::removeMember(const char* begin, const char* end, Value* removed) { if (type() != objectValue) { @@ -1288,31 +1247,13 @@ bool Value::removeMember(const char* begin, const char* end, Value* removed) { value_.map_->erase(it); return true; } -#ifdef JSONCPP_HAS_STRING_VIEW -bool Value::removeMember(std::string_view key, Value* removed) { - return removeMember(key.data(), key.data() + key.length(), removed); -} -#else bool Value::removeMember(const char* key, Value* removed) { return removeMember(key, key + strlen(key), removed); } bool Value::removeMember(String const& key, Value* removed) { return removeMember(key.data(), key.data() + key.length(), removed); } -#endif - -#ifdef JSONCPP_HAS_STRING_VIEW -void Value::removeMember(std::string_view key) { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, - "in Json::Value::removeMember(): requires objectValue"); - if (type() == nullValue) - return; - CZString actualKey(key.data(), unsigned(key.length()), - CZString::noDuplication); - value_.map_->erase(actualKey); -} -#else void Value::removeMember(const char* key) { JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, "in Json::Value::removeMember(): requires objectValue"); @@ -1323,7 +1264,6 @@ void Value::removeMember(const char* key) { value_.map_->erase(actualKey); } void Value::removeMember(const String& key) { removeMember(key.c_str()); } -#endif bool Value::removeIndex(ArrayIndex index, Value* removed) { if (type() != arrayValue) { @@ -1353,18 +1293,12 @@ bool Value::isMember(char const* begin, char const* end) const { Value const* value = find(begin, end); return nullptr != value; } -#ifdef JSONCPP_HAS_STRING_VIEW -bool Value::isMember(std::string_view key) const { - return isMember(key.data(), key.data() + key.length()); -} -#else bool Value::isMember(char const* key) const { return isMember(key, key + strlen(key)); } bool Value::isMember(String const& key) const { return isMember(key.data(), key.data() + key.length()); } -#endif Value::Members Value::getMemberNames() const { JSON_ASSERT_MESSAGE( From 36f94b68d60774d2a5870a6881a92de02ed76eb1 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Thu, 9 Apr 2026 11:01:46 -0700 Subject: [PATCH 52/56] chore: remove leftover CMake checks for std::string_view (#1676) * ci: suppress Node 20 deprecation and missing python-version warnings - Injects `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true` in all workflows to opt-in to Node.js 24 and suppress the deprecation warnings from multiple GitHub Actions. - Specifies `python-version: '3.x'` for `actions/setup-python@v5` in `meson.yml` to fix the missing input warning. * Fix C++11 ABI breakage when compiled with C++17 (#1668) When JSONCPP_HAS_STRING_VIEW was defined, the library dropped the `const char*` and `const String&` overloads for `operator[]`, `get`, `removeMember`, and `isMember`, breaking ABI compatibility for projects consuming the library with C++11. This change unconditionally declares and defines the legacy overloads so they are always exported, restoring compatibility. * ci: add ABI compatibility matrix workflow This adds a new GitHub Actions workflow to verify ABI compatibility across C++ standard boundaries. It explicitly tests the scenario where JsonCpp is built with one standard (e.g., C++11) and consumed by an application built with a newer one (e.g., C++23), and vice versa. To facilitate testing the specific `std::string_view` boundary that is conditionally compiled, a new `stringView` demo application has been added to the `example/` directory and is consumed directly by the CI matrix to ensure standard library symbols link correctly across standard versions, build types (shared/static), and operating systems. * fix: inline std::string_view methods to prevent ABI breaks This commit completely eliminates the ABI breakage that occurs across C++ standard boundaries when using `std::string_view`. Previously, when the library was built with C++17+, CMake would leak `JSONCPP_HAS_STRING_VIEW=1` as a PUBLIC definition. A C++11 consumer would receive this definition, attempt to parse the header, and fail with compiler errors because `std::string_view` is not available in their environment. Conversely, if the library was built in C++11 (without `string_view` symbols), a C++17 consumer would naturally define `JSONCPP_HAS_STRING_VIEW` based on `__cplusplus` inside `value.h`. The consumer would then call the declared `string_view` methods, resulting in linker errors because the methods weren't compiled into the library. By moving all `std::string_view` overloads directly into `value.h` as `inline` methods that delegate to the fundamental `const char*, const char*` methods: 1. The consumer's compiler dictates whether the overloads are visible (via `__cplusplus >= 201703L`). 2. The consumer compiles the inline wrappers locally, removing any reliance on the library's exported symbols for `std::string_view`. 3. CMake no longer needs to pollute the consumer's environment with PUBLIC compile definitions. * run clang format * finish clang format * chore: remove leftover CMake checks for std::string_view Fixes #1669 This removes the final vestige of the JSONCPP_HAS_STRING_VIEW build system logic. As of the previous commit (inlining std::string_view methods into value.h to fix ABI breaks), the library no longer relies on the build system (CMake or Meson) to check for and define JSONCPP_HAS_STRING_VIEW. The header value.h automatically activates std::string_view overloads purely by checking the consumer`s __cplusplus >= 201703L. Since none of the actual std::string_view symbols are compiled into the .so / .a library anymore, Meson (and CMake) builds are identical regardless of whether string_view is supported by the compiler building the library. * format spacing better --- src/lib_json/CMakeLists.txt | 6 ------ src/lib_json/json_value.cpp | 4 ---- 2 files changed, 10 deletions(-) diff --git a/src/lib_json/CMakeLists.txt b/src/lib_json/CMakeLists.txt index a0695e9eb..7197ba793 100644 --- a/src/lib_json/CMakeLists.txt +++ b/src/lib_json/CMakeLists.txt @@ -7,7 +7,6 @@ include(CheckIncludeFileCXX) include(CheckTypeSize) include(CheckStructHasMember) include(CheckCXXSymbolExists) -include(CheckCXXSourceCompiles) check_include_file_cxx(clocale HAVE_CLOCALE) check_cxx_symbol_exists(localeconv clocale HAVE_LOCALECONV) @@ -26,11 +25,6 @@ if(NOT (HAVE_CLOCALE AND HAVE_LCONV_SIZE AND HAVE_DECIMAL_POINT AND HAVE_LOCALEC endif() endif() -check_cxx_source_compiles( - "#include - int main() { std::string_view sv; return 0; }" - JSONCPP_HAS_STRING_VIEW) - set(JSONCPP_INCLUDE_DIR ../../include) set(PUBLIC_HEADERS diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp index a8eb72d6b..a812ea4d3 100644 --- a/src/lib_json/json_value.cpp +++ b/src/lib_json/json_value.cpp @@ -17,10 +17,6 @@ #include #include -#ifdef JSONCPP_HAS_STRING_VIEW -#include -#endif - // Provide implementation equivalent of std::snprintf for older _MSC compilers #if defined(_MSC_VER) && _MSC_VER < 1900 #include From 217973062e42de90f29f7788d8a657b02c57a80d Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Thu, 9 Apr 2026 11:05:57 -0700 Subject: [PATCH 53/56] docs: update amalgamation instructions and add github action (#1677) The amalgamation approach is not outdated and works perfectly out-of-the-box. This updates the README to remove the 'possibly-outdated' warning and replaces it with direct, simple instructions for generating the amalgamated source files. A new GitHub Action workflow (`.github/workflows/amalgamate.yml`) has also been added to ensure the `amalgamate.py` script is run on every commit and that the resulting amalgamated C++ source files successfully compile, preventing any future regressions in the single-header distribution method. --- .github/workflows/amalgamate.yml | 39 ++++++++++++++++++++++++++++++++ README.md | 11 ++++++--- 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/amalgamate.yml diff --git a/.github/workflows/amalgamate.yml b/.github/workflows/amalgamate.yml new file mode 100644 index 000000000..e8a55d428 --- /dev/null +++ b/.github/workflows/amalgamate.yml @@ -0,0 +1,39 @@ +name: Amalgamation + +on: [check_run, push, pull_request] + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + amalgamation: + runs-on: ubuntu-latest + + steps: + - name: checkout project + uses: actions/checkout@v4 + + - name: setup python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: run amalgamate script + run: | + python amalgamate.py + + - name: test compile amalgamated source + run: | + cat << 'EOF' > test_amalgamation.cpp + #include "json/json.h" + #include + + int main() { + Json::Value root; + root["hello"] = "world"; + std::cout << root.toStyledString() << std::endl; + return 0; + } + EOF + c++ -std=c++11 -I dist dist/jsoncpp.cpp test_amalgamation.cpp -o test_amalgamation + ./test_amalgamation \ No newline at end of file diff --git a/README.md b/README.md index 25b2d2b83..fe2b4f956 100644 --- a/README.md +++ b/README.md @@ -88,10 +88,15 @@ meson wrap install jsoncpp ### Amalgamated source -> [!NOTE] -> This approach may be outdated. +For projects requiring a single-header approach, JsonCpp provides a script to generate an amalgamated source and header file. + +You can generate the amalgamated files by running the following Python script from the top-level directory: + +```sh +python3 amalgamate.py +``` -For projects requiring a single-header approach, see the [Wiki entry](https://github.com/open-source-parsers/jsoncpp/wiki/Amalgamated-(Possibly-outdated)). +This will generate a `dist` directory containing `jsoncpp.cpp`, `json/json.h`, and `json/json-forwards.h`. You can then drop these files directly into your project's source tree and compile `jsoncpp.cpp` alongside your other source files. ## Documentation From 941802d466ff6117508e326025720b74d67636f0 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Thu, 9 Apr 2026 11:42:28 -0700 Subject: [PATCH 54/56] feat: add Json::version() to expose runtime version (#1531) (#1678) This adds a runtime function `Json::version()` that returns the `JSONCPP_VERSION_STRING`. This allows a program using jsoncpp to display the version information of the runtime linked shared library, or check at runtime that the version of the shared library is compatible with what the program expects. Fixes #1531 --- include/json/config.h | 1 + src/lib_json/json_value.cpp | 2 ++ src/test_lib_json/main.cpp | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/include/json/config.h b/include/json/config.h index 6971fa656..4619e93bd 100644 --- a/include/json/config.h +++ b/include/json/config.h @@ -105,6 +105,7 @@ extern JSON_API int msvc_pre1900_c99_snprintf(char* outBuf, size_t size, #endif // if !defined(JSON_IS_AMALGAMATION) namespace Json { +JSON_API const char* version(); using Int = int; using UInt = unsigned int; #if defined(JSON_NO_INT64) diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp index a812ea4d3..168251ee1 100644 --- a/src/lib_json/json_value.cpp +++ b/src/lib_json/json_value.cpp @@ -1704,4 +1704,6 @@ Value& Path::make(Value& root) const { return *node; } +const char* version() { return JSONCPP_VERSION_STRING; } + } // namespace Json diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index d6938c1d0..4c000fa9b 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -4188,6 +4188,11 @@ JSONTEST_FIXTURE_LOCAL(VersionTest, VersionNumbersMatch) { JSONTEST_ASSERT_EQUAL(vstr.str(), std::string(JSONCPP_VERSION_STRING)); } +JSONTEST_FIXTURE_LOCAL(VersionTest, RuntimeVersionString) { + JSONTEST_ASSERT_EQUAL(std::string(JSONCPP_VERSION_STRING), + std::string(Json::version())); +} + #if defined(__GNUC__) #pragma GCC diagnostic pop #endif From 64f54a4cfe4af4db1567672e24b033e051706536 Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Thu, 23 Apr 2026 16:20:58 -0700 Subject: [PATCH 55/56] feat: add .members() iterator adapter for range-based for loops (#288) (#1679) * feat: add .members() iterator adapter for range-based for loops (#288) This adds a zero-allocation iterator adapter to `Json::Value` that enables idiomatic range-based for loops over object members. This allows iterating over key-value pairs without allocating a vector of keys via `getMemberNames()`, and cleanly supports C++17 structured bindings (e.g. `for (const auto& [name, val] : obj.members())`). Fixes #288 * run ninja format --- .github/workflows/abi-compatibility.yml | 13 ++- .github/workflows/cmake.yml | 3 + include/json/forwards.h | 2 + include/json/value.h | 130 ++++++++++++++++++++++++ src/test_lib_json/main.cpp | 48 +++++++++ 5 files changed, 194 insertions(+), 2 deletions(-) diff --git a/.github/workflows/abi-compatibility.yml b/.github/workflows/abi-compatibility.yml index 881c4e8e0..1351c09d4 100644 --- a/.github/workflows/abi-compatibility.yml +++ b/.github/workflows/abi-compatibility.yml @@ -15,10 +15,18 @@ jobs: os: [ubuntu-latest, windows-latest, macos-latest] shared_libs: [ON, OFF] include: + - jsoncpp_std: 11 + app_std: 17 + - jsoncpp_std: 17 + app_std: 11 - jsoncpp_std: 11 app_std: 23 - jsoncpp_std: 23 app_std: 11 + - jsoncpp_std: 17 + app_std: 23 + - jsoncpp_std: 23 + app_std: 17 steps: - name: checkout project @@ -47,11 +55,12 @@ jobs: find_package(jsoncpp REQUIRED CONFIG) - add_executable(abi_test stringView.cpp) + add_executable(abi_test jsontest.cpp fuzz.cpp main.cpp) target_link_libraries(abi_test PRIVATE JsonCpp::JsonCpp) EOF - cp $GITHUB_WORKSPACE/example/stringView/stringView.cpp example-app/stringView.cpp + cp src/test_lib_json/*.cpp example-app/ + cp src/test_lib_json/*.h example-app/ - name: build example app (C++${{ matrix.app_std }}) shell: bash diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 55452ac25..2b2666d36 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -12,6 +12,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] + cxx_standard: [11, 17] steps: - name: checkout project @@ -19,4 +20,6 @@ jobs: - name: build project uses: threeal/cmake-action@v2.0.0 + with: + options: CMAKE_CXX_STANDARD=${{ matrix.cxx_standard }} diff --git a/include/json/forwards.h b/include/json/forwards.h index affe33a7f..2887bdd78 100644 --- a/include/json/forwards.h +++ b/include/json/forwards.h @@ -37,6 +37,8 @@ class Value; class ValueIteratorBase; class ValueIterator; class ValueConstIterator; +class ValueMembersView; +class ValueConstMembersView; } // namespace Json diff --git a/include/json/value.h b/include/json/value.h index 2007e6b42..f14a71ce5 100644 --- a/include/json/value.h +++ b/include/json/value.h @@ -682,6 +682,11 @@ class JSON_API Value { iterator begin(); iterator end(); + // \brief Returns a view of member pairs for range-based for loops. + ValueMembersView members(); + // \brief Returns a view of member pairs for range-based for loops. + ValueConstMembersView members() const; + /// \brief Returns a reference to the first element in the `Value`. /// Requires that this value holds an array or json object, with at least one /// element. @@ -1040,6 +1045,131 @@ class JSON_API ValueIterator : public ValueIteratorBase { pointer operator->() const { return const_cast(&deref()); } }; +/** \brief Proxy struct to enable range-based for loops over object members. + */ +struct MemberProxy { + const String name; + Value& value; +}; + +/** \brief Proxy struct to enable range-based for loops over const object + * members. + */ +struct ConstMemberProxy { + const String name; + const Value& value; +}; + +/** \brief Iterator adapter for range-based for loops. + */ +class ValueMembersIterator { +public: + using iterator_category = std::forward_iterator_tag; + using value_type = MemberProxy; + using difference_type = int; + using pointer = MemberProxy*; + using reference = MemberProxy; + + ValueMembersIterator() = default; + explicit ValueMembersIterator(ValueIterator const& iter) : it_(iter) {} + + ValueMembersIterator& operator++() { + ++it_; + return *this; + } + ValueMembersIterator operator++(int) { + ValueMembersIterator temp(*this); + ++*this; + return temp; + } + bool operator==(ValueMembersIterator const& other) const { + return it_ == other.it_; + } + bool operator!=(ValueMembersIterator const& other) const { + return it_ != other.it_; + } + MemberProxy operator*() const { return MemberProxy{it_.name(), *it_}; } + +private: + ValueIterator it_; +}; + +/** \brief Iterator adapter for range-based for loops. + */ +class ValueConstMembersIterator { +public: + using iterator_category = std::forward_iterator_tag; + using value_type = ConstMemberProxy; + using difference_type = int; + using pointer = ConstMemberProxy*; + using reference = ConstMemberProxy; + + ValueConstMembersIterator() = default; + explicit ValueConstMembersIterator(ValueConstIterator const& iter) + : it_(iter) {} + + ValueConstMembersIterator& operator++() { + ++it_; + return *this; + } + ValueConstMembersIterator operator++(int) { + ValueConstMembersIterator temp(*this); + ++*this; + return temp; + } + bool operator==(ValueConstMembersIterator const& other) const { + return it_ == other.it_; + } + bool operator!=(ValueConstMembersIterator const& other) const { + return it_ != other.it_; + } + ConstMemberProxy operator*() const { + return ConstMemberProxy{it_.name(), *it_}; + } + +private: + ValueConstIterator it_; +}; + +/** \brief Range-based for loop adapter for object members. + */ +class ValueMembersView { +public: + ValueMembersView(ValueIterator begin, ValueIterator end) + : begin_(begin), end_(end) {} + ValueMembersIterator begin() const { return ValueMembersIterator(begin_); } + ValueMembersIterator end() const { return ValueMembersIterator(end_); } + +private: + ValueIterator begin_; + ValueIterator end_; +}; + +/** \brief Range-based for loop adapter for object members. + */ +class ValueConstMembersView { +public: + ValueConstMembersView(ValueConstIterator begin, ValueConstIterator end) + : begin_(begin), end_(end) {} + ValueConstMembersIterator begin() const { + return ValueConstMembersIterator(begin_); + } + ValueConstMembersIterator end() const { + return ValueConstMembersIterator(end_); + } + +private: + ValueConstIterator begin_; + ValueConstIterator end_; +}; + +inline ValueMembersView Value::members() { + return ValueMembersView(begin(), end()); +} +inline ValueConstMembersView Value::members() const { + return ValueConstMembersView(begin(), end()); +} + inline void swap(Value& a, Value& b) { a.swap(b); } inline const Value& Value::front() const { return *begin(); } diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index 4c000fa9b..501aba10e 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -3924,6 +3924,54 @@ JSONTEST_FIXTURE_LOCAL(BomTest, notSkipBom) { struct IteratorTest : JsonTest::TestCase {}; +JSONTEST_FIXTURE_LOCAL(IteratorTest, members) { + Json::Value j; + j["k1"] = "a"; + j["k2"] = "b"; + + std::vector keys; + std::vector values; + + for (const auto& member : j.members()) { + keys.push_back(member.name); + values.push_back(member.value.asString()); + } + + JSONTEST_ASSERT((keys == std::vector{"k1", "k2"})); + JSONTEST_ASSERT((values == std::vector{"a", "b"})); + + // Test modification through value reference + for (const auto& member : j.members()) { + member.value = "c"; + } + + JSONTEST_ASSERT(j["k1"].asString() == "c"); + + // Test const members + const Json::Value& cj = j; + keys.clear(); + values.clear(); + + for (const auto& member : cj.members()) { + keys.push_back(member.name); + values.push_back(member.value.asString()); + } + + JSONTEST_ASSERT((keys == std::vector{"k1", "k2"})); + JSONTEST_ASSERT((values == std::vector{"c", "c"})); + +#if __cplusplus >= 201703L + keys.clear(); + values.clear(); + for (auto const& [k, v] : cj.members()) { + keys.push_back(k); + values.push_back(v.asString()); + } + JSONTEST_ASSERT((keys == std::vector{"k1", "k2"})); + JSONTEST_ASSERT((values == std::vector{"c", "c"})); +#endif +} + JSONTEST_FIXTURE_LOCAL(IteratorTest, convert) { Json::Value j; const Json::Value& cj = j; From 755d0a69d7109d465db6196a3c7e1c6f3c62a48f Mon Sep 17 00:00:00 2001 From: Jordan Bayles Date: Thu, 23 Apr 2026 16:34:58 -0700 Subject: [PATCH 56/56] Improve formatting (#1680) * style: format the entire library using clang-format Updated `reformat.sh` to include the `include` and `example` directories, as well as `.inl` files, and ran it across the repository to ensure consistent code styling throughout the library. * ci: fix directory name in clang-format workflow The workflow was checking the `examples` directory, but the directory is actually named `example`. This updates the matrix path to ensure the example files are properly checked during CI. --- .github/workflows/clang-format.yml | 2 +- reformat.sh | 2 +- src/lib_json/json_valueiterator.inl | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index eca3c31f5..ae3096302 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -12,7 +12,7 @@ jobs: matrix: path: - 'src' - - 'examples' + - 'example' - 'include' steps: - uses: actions/checkout@v4 diff --git a/reformat.sh b/reformat.sh index cdc03b1ea..86bc066f1 100755 --- a/reformat.sh +++ b/reformat.sh @@ -1 +1 @@ -find src -name '*.cpp' -or -name '*.h' | xargs clang-format -i +find src include example -name '*.cpp' -or -name '*.h' -or -name '*.inl' | xargs clang-format -i diff --git a/src/lib_json/json_valueiterator.inl b/src/lib_json/json_valueiterator.inl index d6128b8ed..4e77f368b 100644 --- a/src/lib_json/json_valueiterator.inl +++ b/src/lib_json/json_valueiterator.inl @@ -122,8 +122,8 @@ ValueConstIterator::ValueConstIterator( ValueConstIterator::ValueConstIterator(ValueIterator const& other) : ValueIteratorBase(other) {} -ValueConstIterator& ValueConstIterator:: -operator=(const ValueIteratorBase& other) { +ValueConstIterator& +ValueConstIterator::operator=(const ValueIteratorBase& other) { copy(other); return *this; }