Skip to content

Commit 64f54a4

Browse files
authored
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
1 parent 941802d commit 64f54a4

5 files changed

Lines changed: 194 additions & 2 deletions

File tree

.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 src/test_lib_json/*.cpp example-app/
63+
cp 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: 130 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,131 @@ 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
1056+
* members.
1057+
*/
1058+
struct ConstMemberProxy {
1059+
const String name;
1060+
const Value& value;
1061+
};
1062+
1063+
/** \brief Iterator adapter for range-based for loops.
1064+
*/
1065+
class ValueMembersIterator {
1066+
public:
1067+
using iterator_category = std::forward_iterator_tag;
1068+
using value_type = MemberProxy;
1069+
using difference_type = int;
1070+
using pointer = MemberProxy*;
1071+
using reference = MemberProxy;
1072+
1073+
ValueMembersIterator() = default;
1074+
explicit ValueMembersIterator(ValueIterator const& iter) : it_(iter) {}
1075+
1076+
ValueMembersIterator& operator++() {
1077+
++it_;
1078+
return *this;
1079+
}
1080+
ValueMembersIterator operator++(int) {
1081+
ValueMembersIterator temp(*this);
1082+
++*this;
1083+
return temp;
1084+
}
1085+
bool operator==(ValueMembersIterator const& other) const {
1086+
return it_ == other.it_;
1087+
}
1088+
bool operator!=(ValueMembersIterator const& other) const {
1089+
return it_ != other.it_;
1090+
}
1091+
MemberProxy operator*() const { return MemberProxy{it_.name(), *it_}; }
1092+
1093+
private:
1094+
ValueIterator it_;
1095+
};
1096+
1097+
/** \brief Iterator adapter for range-based for loops.
1098+
*/
1099+
class ValueConstMembersIterator {
1100+
public:
1101+
using iterator_category = std::forward_iterator_tag;
1102+
using value_type = ConstMemberProxy;
1103+
using difference_type = int;
1104+
using pointer = ConstMemberProxy*;
1105+
using reference = ConstMemberProxy;
1106+
1107+
ValueConstMembersIterator() = default;
1108+
explicit ValueConstMembersIterator(ValueConstIterator const& iter)
1109+
: it_(iter) {}
1110+
1111+
ValueConstMembersIterator& operator++() {
1112+
++it_;
1113+
return *this;
1114+
}
1115+
ValueConstMembersIterator operator++(int) {
1116+
ValueConstMembersIterator temp(*this);
1117+
++*this;
1118+
return temp;
1119+
}
1120+
bool operator==(ValueConstMembersIterator const& other) const {
1121+
return it_ == other.it_;
1122+
}
1123+
bool operator!=(ValueConstMembersIterator const& other) const {
1124+
return it_ != other.it_;
1125+
}
1126+
ConstMemberProxy operator*() const {
1127+
return ConstMemberProxy{it_.name(), *it_};
1128+
}
1129+
1130+
private:
1131+
ValueConstIterator it_;
1132+
};
1133+
1134+
/** \brief Range-based for loop adapter for object members.
1135+
*/
1136+
class ValueMembersView {
1137+
public:
1138+
ValueMembersView(ValueIterator begin, ValueIterator end)
1139+
: begin_(begin), end_(end) {}
1140+
ValueMembersIterator begin() const { return ValueMembersIterator(begin_); }
1141+
ValueMembersIterator end() const { return ValueMembersIterator(end_); }
1142+
1143+
private:
1144+
ValueIterator begin_;
1145+
ValueIterator end_;
1146+
};
1147+
1148+
/** \brief Range-based for loop adapter for object members.
1149+
*/
1150+
class ValueConstMembersView {
1151+
public:
1152+
ValueConstMembersView(ValueConstIterator begin, ValueConstIterator end)
1153+
: begin_(begin), end_(end) {}
1154+
ValueConstMembersIterator begin() const {
1155+
return ValueConstMembersIterator(begin_);
1156+
}
1157+
ValueConstMembersIterator end() const {
1158+
return ValueConstMembersIterator(end_);
1159+
}
1160+
1161+
private:
1162+
ValueConstIterator begin_;
1163+
ValueConstIterator end_;
1164+
};
1165+
1166+
inline ValueMembersView Value::members() {
1167+
return ValueMembersView(begin(), end());
1168+
}
1169+
inline ValueConstMembersView Value::members() const {
1170+
return ValueConstMembersView(begin(), end());
1171+
}
1172+
10431173
inline void swap(Value& a, Value& b) { a.swap(b); }
10441174

10451175
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)