Skip to content

Commit 94e2a98

Browse files
committed
Process JSON Command - Refactor JSON path handling and root support
@Mimigris found some missing usecases for Process JSON command. One of them related to missing access to the root of a JSON, for things like `GetKeys("/")` This PR Introduces a helper to handle JSON path resolution, including root path ('/'), and refactors all relevant functions to use it. Improves consistency, reduces code duplication, and adds explicit handling for root object operations in GetValue, SetValue, RemoveValue, Contains, and related methods.
1 parent 14f9507 commit 94e2a98

1 file changed

Lines changed: 94 additions & 106 deletions

File tree

src/json_helper.cpp

Lines changed: 94 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,23 @@ namespace {
4747
return json_obj.dump();
4848
}
4949

50+
// Helper to get a reference to the target json value, handling the root path case.
51+
template <typename JsonType>
52+
auto GetJsonTarget(JsonType& json_obj, std::string_view json_path) -> decltype(&json_obj) {
53+
if (json_path == "/") {
54+
return &json_obj;
55+
}
56+
57+
std::string path_str(json_path);
58+
json::json_pointer ptr(path_str);
59+
60+
if (json_obj.contains(ptr)) {
61+
return &json_obj[ptr];
62+
}
63+
64+
return nullptr;
65+
}
66+
5067
} // namespace
5168

5269
namespace Json_Helper {
@@ -74,99 +91,75 @@ namespace Json_Helper {
7491
}
7592

7693
std::string GetValue(json& json_obj, std::string_view json_path) {
77-
std::string path_str = std::string(json_path);
78-
json::json_pointer ptr(path_str);
79-
80-
if (!json_obj.contains(ptr)) {
81-
return {};
94+
if (auto* target = GetJsonTarget(json_obj, json_path)) {
95+
return GetValueAsString(*target);
8296
}
83-
84-
const json& value = json_obj[ptr];
85-
auto val = GetValueAsString(value);
86-
return val;
97+
return {};
8798
}
8899

89-
90100
std::string SetValue(json& json_obj, std::string_view json_path, std::string_view value) {
91-
std::string path_str = std::string(json_path);
92-
json::json_pointer ptr(path_str);
93-
94101
json obj_value = json::parse(value, nullptr, false);
95102
if (obj_value.is_discarded()) {
96-
// If parsing fails, treat it as a string value
97-
json_obj[ptr] = std::string(value);
103+
obj_value = std::string(value);
104+
}
105+
106+
if (json_path == "/") {
107+
json_obj = obj_value;
98108
}
99109
else {
110+
std::string path_str(json_path);
111+
json::json_pointer ptr(path_str);
100112
json_obj[ptr] = obj_value;
101113
}
102114

103115
return json_obj.dump();
104116
}
105117

106118
size_t GetLength(const json& json_obj, std::string_view json_path) {
107-
std::string path_str = std::string(json_path);
108-
json::json_pointer ptr(path_str);
109-
110-
if (!json_obj.contains(ptr)) {
111-
return 0;
112-
}
113-
114-
const json& value = json_obj[ptr];
115-
if (!value.is_array() && !value.is_object()) {
116-
return 0;
119+
if (const auto* target = GetJsonTarget(json_obj, json_path)) {
120+
if (target->is_array() || target->is_object()) {
121+
return target->size();
122+
}
117123
}
118-
119-
return value.size();
124+
return 0;
120125
}
121126

122127
std::vector<std::string> GetKeys(const json& json_obj, std::string_view json_path) {
123-
std::string path_str = std::string(json_path);
124-
json::json_pointer ptr(path_str);
125-
if (!json_obj.contains(ptr)) {
126-
return {};
127-
}
128-
129-
const json& value = json_obj[ptr];
130-
131128
std::vector<std::string> keys;
132-
133-
if (value.is_object()) {
134-
for (const auto& item : value.items()) {
135-
keys.push_back(item.key());
129+
if (const auto* target = GetJsonTarget(json_obj, json_path)) {
130+
if (target->is_object()) {
131+
for (const auto& item : target->items()) {
132+
keys.push_back(item.key());
133+
}
136134
}
137-
}
138-
else if (value.is_array()) {
139-
for (size_t i = 0; i < value.size(); ++i) {
140-
keys.push_back(std::to_string(i));
135+
else if (target->is_array()) {
136+
for (size_t i = 0; i < target->size(); ++i) {
137+
keys.push_back(std::to_string(i));
138+
}
141139
}
142140
}
143141
return keys;
144142
}
145143

146144
std::string GetType(const json& json_obj, std::string_view json_path) {
147-
std::string path_str = std::string(json_path);
148-
json::json_pointer ptr(path_str);
149-
if (!json_obj.contains(ptr)) {
150-
return {};
145+
if (const auto* value = GetJsonTarget(json_obj, json_path)) {
146+
if (value->is_object()) return std::string("object");
147+
if (value->is_array()) return std::string("array");
148+
if (value->is_string()) return std::string("string");
149+
if (value->is_number()) return std::string("number");
150+
if (value->is_boolean()) return std::string("boolean");
151+
if (value->is_null()) return std::string("null");
152+
return std::string("unknown");
151153
}
152-
153-
const json& value = json_obj[ptr];
154-
155-
if (value.is_object()) return std::string("object");
156-
if (value.is_array()) return std::string("array");
157-
if (value.is_string()) return std::string("string");
158-
if (value.is_number()) return std::string("number");
159-
if (value.is_boolean()) return std::string("boolean");
160-
if (value.is_null()) return std::string("null");
161-
return std::string("unknown");
154+
return {};
162155
}
163156

164157
std::string GetPath(const json& json_obj, const json& search_value) {
165158
std::function<std::string(const json&, const json&, const std::string&)> find_path;
166159

167160
find_path = [&find_path](const json& obj, const json& target, const std::string& current_path) -> std::string {
168161
if (obj == target) {
169-
return current_path;
162+
return current_path.empty() ? "/" : current_path;
170163
}
171164

172165
if (obj.is_object()) {
@@ -192,32 +185,35 @@ namespace Json_Helper {
192185
}
193186

194187
std::string RemoveValue(json& json_obj, std::string_view json_path) {
195-
std::string path_str = std::string(json_path);
188+
// Per user feedback, removing the root clears the object.
189+
if (json_path == "/") {
190+
// .clear() correctly handles both objects ({}) and arrays ([]).
191+
json_obj.clear();
192+
return json_obj.dump();
193+
}
194+
195+
std::string path_str(json_path);
196196
json::json_pointer ptr(path_str);
197197

198198
if (!json_obj.contains(ptr)) {
199199
return {};
200200
}
201201

202-
// Get parent path and key/index to remove
203202
auto parent_ptr = ptr.parent_pointer();
204-
205203
json& parent = json_obj[parent_ptr];
206-
json_path.remove_prefix(parent_ptr.to_string().size() + 1);
204+
const std::string& key = ptr.back();
207205

208206
if (parent.is_object()) {
209-
parent.erase(std::string(json_path));
207+
parent.erase(key);
210208
}
211209
else if (parent.is_array()) {
212-
// Check if key is a valid positive number
213210
unsigned index;
214-
auto ec = std::from_chars(json_path.data(), json_path.data() + json_path.size(), index).ec;
215-
if (ec == std::errc()) {
216-
if (index < parent.size()) {
217-
parent.erase(index);
218-
}
219-
} else {
220-
Output::Warning("JSON: Invalid array index at: {}", json_path);
211+
auto [p, ec] = std::from_chars(key.data(), key.data() + key.size(), index);
212+
if (ec == std::errc() && index < parent.size()) {
213+
parent.erase(index);
214+
}
215+
else {
216+
Output::Warning("JSON: Invalid array index for removal at: {}", json_path);
221217
return {};
222218
}
223219
}
@@ -226,53 +222,45 @@ namespace Json_Helper {
226222
}
227223

228224
std::string PushValue(json& json_obj, std::string_view json_path, std::string_view value) {
229-
std::string path_str = std::string(json_path);
230-
json::json_pointer ptr(path_str);
231-
232-
if (!json_obj.contains(ptr)) {
233-
return {};
234-
}
225+
if (auto* target = GetJsonTarget(json_obj, json_path)) {
226+
if (!target->is_array()) {
227+
Output::Warning("JSON: Path does not point to an array: {}", json_path);
228+
return {};
229+
}
235230

236-
json& array = json_obj[ptr];
237-
if (!array.is_array()) {
238-
Output::Warning("JSON: Path does not point to an array: {}", json_path);
239-
return {};
240-
}
231+
json obj_value = json::parse(value, nullptr, false);
232+
if (obj_value.is_discarded()) {
233+
target->push_back(std::string(value));
234+
}
235+
else {
236+
target->push_back(obj_value);
237+
}
241238

242-
json obj_value = json::parse(value, nullptr, false);
243-
if (obj_value.is_discarded()) {
244-
// If parsing fails, treat it as a string value
245-
array.push_back(std::string(value));
239+
return json_obj.dump();
246240
}
247-
else {
248-
array.push_back(obj_value);
249-
}
250-
251-
return json_obj.dump();
241+
return {};
252242
}
253243

254244
std::tuple<std::string, std::string> PopValue(json& json_obj, std::string_view json_path) {
255-
std::string path_str = std::string(json_path);
256-
json::json_pointer ptr(path_str);
245+
if (auto* target = GetJsonTarget(json_obj, json_path)) {
246+
if (!target->is_array() || target->empty()) {
247+
Output::Warning("JSON: Path does not point to a non-empty array: {}", json_path);
248+
return {};
249+
}
257250

258-
if (!json_obj.contains(ptr)) {
259-
return {};
260-
}
251+
json popped = target->back();
252+
target->erase(target->size() - 1);
261253

262-
json& array = json_obj[ptr];
263-
if (!array.is_array() || array.empty()) {
264-
Output::Warning("JSON: Path does not point to a non-empty array: {}", json_path);
265-
return {};
254+
return { json_obj.dump(), GetValueAsString(popped) };
266255
}
267-
268-
json popped = array.back();
269-
array.erase(array.size() - 1);
270-
271-
return {json_obj.dump(), GetValueAsString(popped)};
256+
return {};
272257
}
273258

274259
bool Contains(const json& json_obj, std::string_view json_path) {
275-
std::string path_str = std::string(json_path);
260+
if (json_path == "/") {
261+
return true;
262+
}
263+
std::string path_str(json_path);
276264
json::json_pointer ptr(path_str);
277265

278266
return json_obj.contains(ptr);

0 commit comments

Comments
 (0)