Skip to content

Commit 59a91db

Browse files
committed
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
1 parent c67034e commit 59a91db

File tree

5 files changed

+176
-2
lines changed

5 files changed

+176
-2
lines changed

.github/workflows/abi-compatibility.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ jobs:
1515
os: [ubuntu-latest, windows-latest, macos-latest]
1616
shared_libs: [ON, OFF]
1717
include:
18+
- jsoncpp_std: 11
19+
app_std: 17
20+
- jsoncpp_std: 17
21+
app_std: 11
1822
- jsoncpp_std: 11
1923
app_std: 23
2024
- jsoncpp_std: 23
2125
app_std: 11
26+
- jsoncpp_std: 17
27+
app_std: 23
28+
- jsoncpp_std: 23
29+
app_std: 17
2230

2331
steps:
2432
- name: checkout project
@@ -47,11 +55,12 @@ jobs:
4755
4856
find_package(jsoncpp REQUIRED CONFIG)
4957
50-
add_executable(abi_test stringView.cpp)
58+
add_executable(abi_test jsontest.cpp fuzz.cpp main.cpp)
5159
target_link_libraries(abi_test PRIVATE JsonCpp::JsonCpp)
5260
EOF
5361
54-
cp $GITHUB_WORKSPACE/example/stringView/stringView.cpp example-app/stringView.cpp
62+
cp $GITHUB_WORKSPACE/src/test_lib_json/*.cpp example-app/
63+
cp $GITHUB_WORKSPACE/src/test_lib_json/*.h example-app/
5564
5665
- name: build example app (C++${{ matrix.app_std }})
5766
shell: bash

.github/workflows/cmake.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ jobs:
1212
fail-fast: false
1313
matrix:
1414
os: [ubuntu-latest, windows-latest, macos-latest]
15+
cxx_standard: [11, 17]
1516

1617
steps:
1718
- name: checkout project
1819
uses: actions/checkout@v4
1920

2021
- name: build project
2122
uses: threeal/cmake-action@v2.0.0
23+
with:
24+
options: CMAKE_CXX_STANDARD=${{ matrix.cxx_standard }}
2225

include/json/forwards.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class Value;
3737
class ValueIteratorBase;
3838
class ValueIterator;
3939
class ValueConstIterator;
40+
class ValueMembersView;
41+
class ValueConstMembersView;
4042

4143
} // namespace Json
4244

include/json/value.h

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,11 @@ class JSON_API Value {
682682
iterator begin();
683683
iterator end();
684684

685+
// \brief Returns a view of member pairs for range-based for loops.
686+
ValueMembersView members();
687+
// \brief Returns a view of member pairs for range-based for loops.
688+
ValueConstMembersView members() const;
689+
685690
/// \brief Returns a reference to the first element in the `Value`.
686691
/// Requires that this value holds an array or json object, with at least one
687692
/// element.
@@ -1040,6 +1045,113 @@ class JSON_API ValueIterator : public ValueIteratorBase {
10401045
pointer operator->() const { return const_cast<pointer>(&deref()); }
10411046
};
10421047

1048+
/** \brief Proxy struct to enable range-based for loops over object members.
1049+
*/
1050+
struct MemberProxy {
1051+
const String name;
1052+
Value& value;
1053+
};
1054+
1055+
/** \brief Proxy struct to enable range-based for loops over const object members.
1056+
*/
1057+
struct ConstMemberProxy {
1058+
const String name;
1059+
const Value& value;
1060+
};
1061+
1062+
/** \brief Iterator adapter for range-based for loops.
1063+
*/
1064+
class ValueMembersIterator {
1065+
public:
1066+
using iterator_category = std::forward_iterator_tag;
1067+
using value_type = MemberProxy;
1068+
using difference_type = int;
1069+
using pointer = MemberProxy*;
1070+
using reference = MemberProxy;
1071+
1072+
ValueMembersIterator() = default;
1073+
explicit ValueMembersIterator(ValueIterator const& iter) : it_(iter) {}
1074+
1075+
ValueMembersIterator& operator++() {
1076+
++it_;
1077+
return *this;
1078+
}
1079+
ValueMembersIterator operator++(int) {
1080+
ValueMembersIterator temp(*this);
1081+
++*this;
1082+
return temp;
1083+
}
1084+
bool operator==(ValueMembersIterator const& other) const { return it_ == other.it_; }
1085+
bool operator!=(ValueMembersIterator const& other) const { return it_ != other.it_; }
1086+
MemberProxy operator*() const { return MemberProxy{it_.name(), *it_}; }
1087+
1088+
private:
1089+
ValueIterator it_;
1090+
};
1091+
1092+
/** \brief Iterator adapter for range-based for loops.
1093+
*/
1094+
class ValueConstMembersIterator {
1095+
public:
1096+
using iterator_category = std::forward_iterator_tag;
1097+
using value_type = ConstMemberProxy;
1098+
using difference_type = int;
1099+
using pointer = ConstMemberProxy*;
1100+
using reference = ConstMemberProxy;
1101+
1102+
ValueConstMembersIterator() = default;
1103+
explicit ValueConstMembersIterator(ValueConstIterator const& iter) : it_(iter) {}
1104+
1105+
ValueConstMembersIterator& operator++() {
1106+
++it_;
1107+
return *this;
1108+
}
1109+
ValueConstMembersIterator operator++(int) {
1110+
ValueConstMembersIterator temp(*this);
1111+
++*this;
1112+
return temp;
1113+
}
1114+
bool operator==(ValueConstMembersIterator const& other) const { return it_ == other.it_; }
1115+
bool operator!=(ValueConstMembersIterator const& other) const { return it_ != other.it_; }
1116+
ConstMemberProxy operator*() const { return ConstMemberProxy{it_.name(), *it_}; }
1117+
1118+
private:
1119+
ValueConstIterator it_;
1120+
};
1121+
1122+
/** \brief Range-based for loop adapter for object members.
1123+
*/
1124+
class ValueMembersView {
1125+
public:
1126+
ValueMembersView(ValueIterator begin, ValueIterator end) : begin_(begin), end_(end) {}
1127+
ValueMembersIterator begin() const { return ValueMembersIterator(begin_); }
1128+
ValueMembersIterator end() const { return ValueMembersIterator(end_); }
1129+
1130+
private:
1131+
ValueIterator begin_;
1132+
ValueIterator end_;
1133+
};
1134+
1135+
/** \brief Range-based for loop adapter for object members.
1136+
*/
1137+
class ValueConstMembersView {
1138+
public:
1139+
ValueConstMembersView(ValueConstIterator begin, ValueConstIterator end) : begin_(begin), end_(end) {}
1140+
ValueConstMembersIterator begin() const { return ValueConstMembersIterator(begin_); }
1141+
ValueConstMembersIterator end() const { return ValueConstMembersIterator(end_); }
1142+
1143+
private:
1144+
ValueConstIterator begin_;
1145+
ValueConstIterator end_;
1146+
};
1147+
1148+
inline ValueMembersView Value::members() {
1149+
return ValueMembersView(begin(), end());
1150+
}
1151+
inline ValueConstMembersView Value::members() const {
1152+
return ValueConstMembersView(begin(), end());
1153+
}
1154+
10431155
inline void swap(Value& a, Value& b) { a.swap(b); }
10441156

10451157
inline const Value& Value::front() const { return *begin(); }

src/test_lib_json/main.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3924,6 +3924,54 @@ JSONTEST_FIXTURE_LOCAL(BomTest, notSkipBom) {
39243924

39253925
struct IteratorTest : JsonTest::TestCase {};
39263926

3927+
JSONTEST_FIXTURE_LOCAL(IteratorTest, members) {
3928+
Json::Value j;
3929+
j["k1"] = "a";
3930+
j["k2"] = "b";
3931+
3932+
std::vector<std::string> keys;
3933+
std::vector<std::string> values;
3934+
3935+
for (const auto& member : j.members()) {
3936+
keys.push_back(member.name);
3937+
values.push_back(member.value.asString());
3938+
}
3939+
3940+
JSONTEST_ASSERT((keys == std::vector<std::string>{"k1", "k2"}));
3941+
JSONTEST_ASSERT((values == std::vector<std::string>{"a", "b"}));
3942+
3943+
// Test modification through value reference
3944+
for (const auto& member : j.members()) {
3945+
member.value = "c";
3946+
}
3947+
3948+
JSONTEST_ASSERT(j["k1"].asString() == "c");
3949+
3950+
// Test const members
3951+
const Json::Value& cj = j;
3952+
keys.clear();
3953+
values.clear();
3954+
3955+
for (const auto& member : cj.members()) {
3956+
keys.push_back(member.name);
3957+
values.push_back(member.value.asString());
3958+
}
3959+
3960+
JSONTEST_ASSERT((keys == std::vector<std::string>{"k1", "k2"}));
3961+
JSONTEST_ASSERT((values == std::vector<std::string>{"c", "c"}));
3962+
3963+
#if __cplusplus >= 201703L
3964+
keys.clear();
3965+
values.clear();
3966+
for (auto const& [k, v] : cj.members()) {
3967+
keys.push_back(k);
3968+
values.push_back(v.asString());
3969+
}
3970+
JSONTEST_ASSERT((keys == std::vector<std::string>{"k1", "k2"}));
3971+
JSONTEST_ASSERT((values == std::vector<std::string>{"c", "c"}));
3972+
#endif
3973+
}
3974+
39273975
JSONTEST_FIXTURE_LOCAL(IteratorTest, convert) {
39283976
Json::Value j;
39293977
const Json::Value& cj = j;

0 commit comments

Comments
 (0)