Skip to content

Commit 31de2a7

Browse files
authored
Merge pull request #3455 from EasyRPG-NewFeatures/ProcessJSON-Fixes
Process JSON Command - Refactor JSON path handling and root support
2 parents fe1cfac + e820444 commit 31de2a7

File tree

2 files changed

+121
-119
lines changed

2 files changed

+121
-119
lines changed

src/json_helper.cpp

Lines changed: 93 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121

2222
#include "output.h"
2323
#include <nlohmann/json.hpp>
24-
#include <sstream>
25-
#include <unordered_map>
2624
#include <charconv>
2725
#include "string_view.h"
2826

@@ -47,6 +45,23 @@ namespace {
4745
return json_obj.dump();
4846
}
4947

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

5267
namespace Json_Helper {
@@ -74,99 +89,76 @@ namespace Json_Helper {
7489
}
7590

7691
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 {};
92+
if (auto* target = GetJsonTarget(json_obj, json_path); target) {
93+
return GetValueAsString(*target);
8294
}
83-
84-
const json& value = json_obj[ptr];
85-
auto val = GetValueAsString(value);
86-
return val;
95+
return {};
8796
}
8897

89-
9098
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-
9499
json obj_value = json::parse(value, nullptr, false);
95100
if (obj_value.is_discarded()) {
96101
// If parsing fails, treat it as a string value
97-
json_obj[ptr] = std::string(value);
102+
obj_value = std::string(value);
103+
}
104+
105+
if (json_path == "/") {
106+
json_obj = obj_value;
98107
}
99108
else {
109+
std::string path_str(json_path);
110+
json::json_pointer ptr(path_str);
100111
json_obj[ptr] = obj_value;
101112
}
102113

103114
return json_obj.dump();
104115
}
105116

106117
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;
118+
if (auto* target = GetJsonTarget(json_obj, json_path); target) {
119+
if (target->is_array() || target->is_object()) {
120+
return target->size();
121+
}
117122
}
118-
119-
return value.size();
123+
return 0;
120124
}
121125

122126
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-
131127
std::vector<std::string> keys;
132-
133-
if (value.is_object()) {
134-
for (const auto& item : value.items()) {
135-
keys.push_back(item.key());
128+
if (auto* target = GetJsonTarget(json_obj, json_path); target) {
129+
if (target->is_object()) {
130+
for (const auto& item : target->items()) {
131+
keys.push_back(item.key());
132+
}
136133
}
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));
134+
else if (target->is_array()) {
135+
for (size_t i = 0; i < target->size(); ++i) {
136+
keys.push_back(std::to_string(i));
137+
}
141138
}
142139
}
143140
return keys;
144141
}
145142

146143
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 {};
144+
if (const auto* value = GetJsonTarget(json_obj, json_path)) {
145+
if (value->is_object()) return std::string("object");
146+
if (value->is_array()) return std::string("array");
147+
if (value->is_string()) return std::string("string");
148+
if (value->is_number()) return std::string("number");
149+
if (value->is_boolean()) return std::string("boolean");
150+
if (value->is_null()) return std::string("null");
151+
return std::string("unknown");
151152
}
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");
153+
return {};
162154
}
163155

164156
std::string GetPath(const json& json_obj, const json& search_value) {
165157
std::function<std::string(const json&, const json&, const std::string&)> find_path;
166158

167159
find_path = [&find_path](const json& obj, const json& target, const std::string& current_path) -> std::string {
168160
if (obj == target) {
169-
return current_path;
161+
return current_path.empty() ? "/" : current_path;
170162
}
171163

172164
if (obj.is_object()) {
@@ -192,32 +184,37 @@ namespace Json_Helper {
192184
}
193185

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

198197
if (!json_obj.contains(ptr)) {
199198
return {};
200199
}
201200

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

208205
if (parent.is_object()) {
209-
parent.erase(std::string(json_path));
206+
parent.erase(key);
210207
}
211208
else if (parent.is_array()) {
212-
// Check if key is a valid positive number
213209
unsigned index;
214-
auto ec = std::from_chars(json_path.data(), json_path.data() + json_path.size(), index).ec;
210+
auto [p, ec] = std::from_chars(key.data(), key.data() + key.size(), index);
215211
if (ec == std::errc()) {
216212
if (index < parent.size()) {
217213
parent.erase(index);
218214
}
219-
} else {
220-
Output::Warning("JSON: Invalid array index at: {}", json_path);
215+
}
216+
else {
217+
Output::Warning("JSON: Invalid array index for removal at: {}", json_path);
221218
return {};
222219
}
223220
}
@@ -226,53 +223,46 @@ namespace Json_Helper {
226223
}
227224

228225
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-
}
226+
if (auto* target = GetJsonTarget(json_obj, json_path); target) {
227+
if (!target->is_array()) {
228+
Output::Warning("JSON: Path does not point to an array: {}", json_path);
229+
return {};
230+
}
235231

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-
}
232+
json obj_value = json::parse(value, nullptr, false);
233+
if (obj_value.is_discarded()) {
234+
// If parsing fails, treat it as a string value
235+
target->push_back(std::string(value));
236+
}
237+
else {
238+
target->push_back(obj_value);
239+
}
241240

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));
241+
return json_obj.dump();
246242
}
247-
else {
248-
array.push_back(obj_value);
249-
}
250-
251-
return json_obj.dump();
243+
return {};
252244
}
253245

254246
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);
247+
if (auto* target = GetJsonTarget(json_obj, json_path); target) {
248+
if (!target->is_array() || target->empty()) {
249+
Output::Warning("JSON: Path does not point to a non-empty array: {}", json_path);
250+
return {};
251+
}
257252

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

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 {};
256+
return { json_obj.dump(), GetValueAsString(popped) };
266257
}
267-
268-
json popped = array.back();
269-
array.erase(array.size() - 1);
270-
271-
return {json_obj.dump(), GetValueAsString(popped)};
258+
return {};
272259
}
273260

274261
bool Contains(const json& json_obj, std::string_view json_path) {
275-
std::string path_str = std::string(json_path);
262+
if (json_path == "/") {
263+
return true;
264+
}
265+
std::string path_str(json_path);
276266
json::json_pointer ptr(path_str);
277267

278268
return json_obj.contains(ptr);

tests/json.cpp

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -119,38 +119,49 @@ TEST_CASE("Get Path") {
119119
CHECK_EQ(Json_Helper::GetPath(obj, "/missing"), "");
120120
}
121121

122+
TEST_CASE("Remove Root Element") {
123+
// Clears the object
124+
auto obj = load("obj.json");
125+
Json_Helper::RemoveValue(obj, "/");
126+
CHECK_EQ(obj.dump(), "{}");
127+
}
128+
122129
TEST_CASE("Remove Value") {
123130
auto obj = load("obj.json");
124131

125132
auto remove = [&](std::string_view path) {
126133
Json_Helper::RemoveValue(obj, path);
127134
};
128135

136+
// Test removing a top-level key
129137
{
130-
auto orig = obj;
131-
remove("/");
132-
CHECK_EQ(obj.dump(), orig.dump());
138+
auto orig = obj;
139+
remove("/name");
140+
orig.erase("name");
141+
CHECK_EQ(obj.dump(), orig.dump());
133142
}
134143

144+
// Test removing an element from an array
135145
{
136-
auto orig = obj;
137-
remove("/name");
138-
orig.erase("name");
139-
CHECK_EQ(obj.dump(), orig.dump());
146+
auto orig = obj;
147+
remove("/atk/1");
148+
orig["atk"].erase(1);
149+
CHECK_EQ(obj.dump(), orig.dump());
140150
}
141151

152+
// Test removing a key from a nested object
142153
{
143-
auto orig = obj;
144-
remove("/atk/1");
145-
orig["atk"].erase(1);
146-
CHECK_EQ(obj.dump(), orig.dump());
154+
auto orig = obj;
155+
remove("/skills/0/name");
156+
orig["skills"][0].erase("name");
157+
CHECK_EQ(obj.dump(), orig.dump());
147158
}
148159

160+
// Test that removing a non-existent path does nothing
149161
{
150-
auto orig = obj;
151-
remove("/skills/0/name");
152-
orig["skills"][0].erase("name");
153-
CHECK_EQ(obj.dump(), orig.dump());
162+
auto orig = obj;
163+
Json_Helper::RemoveValue(obj, "/missing_path");
164+
CHECK_EQ(obj.dump(), orig.dump());
154165
}
155166
}
156167

@@ -166,7 +177,7 @@ TEST_CASE("Push Value") {
166177
CHECK_EQ(orig, obj.dump());
167178

168179
push("/atk", "3");
169-
CHECK_EQ(obj["atk"], std::vector<int>{1,5,10,30,20,3});
180+
CHECK_EQ(obj["atk"], std::vector<json>{1, 5, 10, 30, 20, 3});
170181
}
171182

172183
TEST_CASE("Pop Value") {
@@ -200,6 +211,7 @@ TEST_CASE("Contains") {
200211
return Json_Helper::Contains(obj, path);
201212
};
202213

214+
CHECK_EQ(check("/"), true);
203215
CHECK_EQ(check("/name"), true);
204216
CHECK_EQ(check("/atk"), true);
205217
CHECK_EQ(check("/skills/0"), true);

0 commit comments

Comments
 (0)