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;