From e632a3735ab9cd95ec09b7ef4d64552cfd2bc5fb Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:21:05 -0300 Subject: [PATCH] PROCESS JSON COMMAND - New Features The Operations were updated to: 0: GET 1: SET 2: GET LENGTH 3: GET KEYS 4: GET VAR TYPE There are also new Flags: - Extract data from json path - similar to how stringvars extracts. - Prettify json - to make its output easier to read. --- src/game_interpreter.cpp | 86 ++++++++++++- src/json_helper.cpp | 265 +++++++++++++++++++++++++++++++-------- src/json_helper.h | 91 +++++++++++++- 3 files changed, 380 insertions(+), 62 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 91821c45e0..292a4467f9 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5363,6 +5363,17 @@ bool Game_Interpreter::CommandEasyRpgProcessJson(lcf::rpg::EventCommand const& c int target_var_id = ValueOrVariable(com.parameters[6], com.parameters[7]); std::string json_path = ToString(CommandStringOrVariable(com, 8, 9)); + + int extract_data_from_string = com.parameters[11]; + bool pretty_print = com.parameters[12] == 1; + + if (extract_data_from_string == 1) { // as string + json_path = Game_Strings::Extract(json_path, false); + } + if (extract_data_from_string == 2) { // as hex + json_path = Game_Strings::Extract(json_path, true); + } + auto* json_data = Main_Data::game_strings->ParseJson(source_var_id); if (!json_data) { @@ -5377,7 +5388,8 @@ bool Game_Interpreter::CommandEasyRpgProcessJson(lcf::rpg::EventCommand const& c std::optional result; - if (operation == 0) { // Get operation: Extract a value from JSON data + switch (operation) { + case 0: { // Get operation: Extract a value from JSON data result = Json_Helper::GetValue(*json_data, json_path); if (result) { @@ -5392,12 +5404,13 @@ bool Game_Interpreter::CommandEasyRpgProcessJson(lcf::rpg::EventCommand const& c Main_Data::game_strings->Asg({ target_var_id }, *result); break; default: - Output::Warning("CommandEasyRpgProcessJson: Unsupported target_var_type {}", operation); + Output::Warning("CommandEasyRpgProcessJson: Unsupported target_var_type {}", target_var_type); return true; } } + break; } - else if (operation == 1) { // Set operation: Update JSON data with a new value + case 1: { // Set operation: Update JSON data with a new value std::string new_value; switch (target_var_type) { @@ -5420,11 +5433,76 @@ bool Game_Interpreter::CommandEasyRpgProcessJson(lcf::rpg::EventCommand const& c if (result) { Main_Data::game_strings->Asg({ source_var_id }, *result); } + break; + } + case 2: { // GetLength operation + auto length = Json_Helper::GetLength(*json_data, json_path); + if (length) { + switch (target_var_type) { + case 0: // Switch + Main_Data::game_switches->Set(target_var_id, *length > 0); + break; + case 1: // Variable + Main_Data::game_variables->Set(target_var_id, static_cast(*length)); + break; + case 2: // String + Main_Data::game_strings->Asg({ target_var_id }, std::to_string(*length)); + break; + } + } + break; + } + case 3: { // GetKeys operation + auto keys = Json_Helper::GetKeys(*json_data, json_path); + if (keys && target_var_type == 2) { // Keys can only be stored in strings + std::string keys_str; + for (size_t i = 0; i < keys->size(); ++i) { + if (i > 0) keys_str += ","; + keys_str += "\"" + (*keys)[i] + "\""; + } + Main_Data::game_strings->Asg({ target_var_id }, "{ \"keys\": [" + keys_str + "] }"); + } + break; } - else { + case 4: { // GetType operation + auto type = Json_Helper::GetType(*json_data, json_path); + if (type) { + int type_code = 0; + switch (target_var_type) { + case 0: // Switch + // For switches, true if it's an object or array (for backward compatibility) + Main_Data::game_switches->Set(target_var_id, *type == "object" || *type == "array"); + break; + case 1: // Variable + // For variables, return a numeric code for the type: + // 1=object, 2=array, 3=string, 4=number, 5=boolean, 6=null + if (*type == "object") type_code = 1; + else if (*type == "array") type_code = 2; + else if (*type == "string") type_code = 3; + else if (*type == "number") type_code = 4; + else if (*type == "boolean") type_code = 5; + else if (*type == "null") type_code = 6; + Main_Data::game_variables->Set(target_var_id, type_code); + break; + case 2: // String + Main_Data::game_strings->Asg({ target_var_id }, *type); + break; + } + } + break; + } + default: Output::Warning("CommandEasyRpgProcessJson: Invalid Operation {}", operation); } + if (target_var_type == 2 && pretty_print == 1) { // Only works with strings + std::string target_str = ToString(Main_Data::game_strings->Get(target_var_id)); + if (auto parsed_json = Json_Helper::Parse(target_str)) { + std::string formatted = Json_Helper::PrettyPrint(*parsed_json, 2); + Main_Data::game_strings->Asg({ target_var_id }, formatted); + } + } + return true; #endif // !HAVE_NLOHMANN_JSON diff --git a/src/json_helper.cpp b/src/json_helper.cpp index 7205f361a1..3b6c760f4c 100644 --- a/src/json_helper.cpp +++ b/src/json_helper.cpp @@ -28,73 +28,230 @@ using json = nlohmann::json; namespace { - std::string GetValueAsString(const json& json_obj) { - std::string result; - - if (json_obj.is_string()) { - result = json_obj.get(); - } - else if (json_obj.is_number_integer()) { - result = std::to_string(json_obj.get()); - } - else if (json_obj.is_number_float()) { - result = std::to_string(json_obj.get()); - } - else if (json_obj.is_boolean()) { - result = json_obj.get() ? "true" : "false"; - } - else { - result = json_obj.dump(); - } - - return result; - } + +std::string GetValueAsString(const json& json_obj) { + if (json_obj.is_null()) { + return "null"; + } + if (json_obj.is_string()) { + return json_obj.get(); + } + if (json_obj.is_number_integer()) { + return std::to_string(json_obj.get()); + } + if (json_obj.is_number_float()) { + return std::to_string(json_obj.get()); + } + if (json_obj.is_boolean()) { + return json_obj.get() ? "true" : "false"; + } + return json_obj.dump(); } +} // namespace + namespace Json_Helper { - std::optional Parse(std::string_view json_data) { - json json_obj = json::parse(json_data, nullptr, false); - if (json_obj.is_discarded()) { - return {}; - } - return json_obj; - } +std::optional Parse(std::string_view json_data) { + json json_obj = json::parse(json_data, nullptr, false); + if (json_obj.is_discarded()) { + return {}; + } + return json_obj; +} + +std::optional GetValue(nlohmann::json& json_obj, std::string_view json_path) { + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer"); + return {}; + } + + try { + auto ptr = json::json_pointer(std::string(json_path)); + if (!json_obj.contains(ptr)) { + return ""; + } + return GetValueAsString(json_obj[ptr]); + } catch (const json::parse_error& e) { + Output::Warning("JSON: Invalid json pointer {} - {}", json_path, e.what()); + return {}; + } +} + +std::string SetValue(nlohmann::json& json_obj, std::string_view json_path, std::string_view value) { + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer"); + return {}; + } + + try { + auto ptr = json::json_pointer(std::string(json_path)); + + json obj_value = json::parse(value, nullptr, false); + if (obj_value.is_discarded()) { + // If parsing fails, treat it as a string value + json_obj[ptr] = std::string(value); + } else { + json_obj[ptr] = obj_value; + } + + return json_obj.dump(); + } catch (const json::parse_error& e) { + Output::Warning("JSON: Invalid json pointer {} - {}", json_path, e.what()); + return {}; + } +} + +std::optional GetLength(const nlohmann::json& json_obj, std::string_view json_path) { + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer"); + return {}; + } - std::optional GetValue(nlohmann::json& json_obj, std::string_view json_path) { - json::json_pointer ptr((std::string(json_path))); + try { + auto ptr = json::json_pointer(std::string(json_path)); + if (!json_obj.contains(ptr)) { + return 0; + } + + const auto& value = json_obj[ptr]; + if (!value.is_array() && !value.is_object()) { + return 0; + } + return value.size(); + } catch (const json::parse_error& e) { + Output::Warning("JSON: Invalid json pointer {} - {}", json_path, e.what()); + return {}; + } +} + +std::optional> GetKeys(const nlohmann::json& json_obj, std::string_view json_path) { + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer"); + return {}; + } + + try { + auto ptr = json::json_pointer(std::string(json_path)); + if (!json_obj.contains(ptr)) { + return std::vector(); + } + + const auto& value = json_obj[ptr]; + std::vector keys; + + if (value.is_object()) { + for (const auto& item : value.items()) { + keys.push_back(item.key()); + } + } else if (value.is_array()) { + for (size_t i = 0; i < value.size(); ++i) { + keys.push_back(std::to_string(i)); + } + } + return keys; + } catch (const json::parse_error& e) { + Output::Warning("JSON: Invalid json pointer {} - {}", json_path, e.what()); + return {}; + } +} + +std::optional IsObject(const nlohmann::json& json_obj, std::string_view json_path) { + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer"); + return {}; + } - if (ptr.empty()) { - Output::Warning("JSON: Bad json pointer {}", json_path); - return {}; - } + try { + auto ptr = json::json_pointer(std::string(json_path)); + return json_obj.contains(ptr) && json_obj[ptr].is_object(); + } catch (const json::parse_error& e) { + Output::Warning("JSON: Invalid json pointer {} - {}", json_path, e.what()); + return {}; + } +} - if (!json_obj.contains(ptr)) { - return ""; - } +std::optional IsArray(const nlohmann::json& json_obj, std::string_view json_path) { + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer"); + return {}; + } - return GetValueAsString(json_obj[ptr]); - } + try { + auto ptr = json::json_pointer(std::string(json_path)); + return json_obj.contains(ptr) && json_obj[ptr].is_array(); + } catch (const json::parse_error& e) { + Output::Warning("JSON: Invalid json pointer {} - {}", json_path, e.what()); + return {}; + } +} - std::string SetValue(nlohmann::json& json_obj, std::string_view json_path, std::string_view value) { - json::json_pointer ptr((std::string(json_path))); +std::optional GetType(const nlohmann::json& json_obj, std::string_view json_path) { + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer"); + return {}; + } - if (ptr.empty()) { - Output::Warning("JSON: Bad json pointer {}", json_path); - return {}; - } + try { + auto ptr = json::json_pointer(std::string(json_path)); + if (!json_obj.contains(ptr)) { + return std::string("null"); + } + + const auto& value = json_obj[ptr]; + if (value.is_object()) return std::string("object"); + if (value.is_array()) return std::string("array"); + if (value.is_string()) return std::string("string"); + if (value.is_number()) return std::string("number"); + if (value.is_boolean()) return std::string("boolean"); + if (value.is_null()) return std::string("null"); + return std::string("unknown"); + } catch (const json::parse_error& e) { + Output::Warning("JSON: Invalid json pointer {} - {}", json_path, e.what()); + return {}; + } +} - json obj_value = json::parse(value, nullptr, false); +std::optional GetPath(const nlohmann::json& json_obj, const nlohmann::json& search_value) { + std::function(const json&, const json&, const std::string&)> find_path; + + find_path = [&find_path](const json& obj, const json& target, const std::string& current_path) -> std::optional { + if (obj == target) { + return current_path; + } + + if (obj.is_object()) { + for (const auto& item : obj.items()) { + auto path = find_path(item.value(), target, current_path + "/" + item.key()); + if (path) return path; + } + } + else if (obj.is_array()) { + for (size_t i = 0; i < obj.size(); ++i) { + auto path = find_path(obj[i], target, current_path + "/" + std::to_string(i)); + if (path) return path; + } + } + return std::nullopt; + }; - if (obj_value.is_discarded()) { - // If parsing fails, treat it as a string value - json_obj[ptr] = std::string(value); - } else { - json_obj[ptr] = obj_value; - } + try { + auto path = find_path(json_obj, search_value, ""); + return path.value_or(""); + } catch (...) { + Output::Warning("JSON: Error while searching for path"); + return {}; + } +} - return json_obj.dump(); - } +std::string PrettyPrint(const nlohmann::json& json_obj, int indent) { + try { + return json_obj.dump(std::max(0, indent)); + } catch (...) { + return "{}"; + } } +} // namespace Json_Helper + #endif // HAVE_NLOHMANN_JSON diff --git a/src/json_helper.h b/src/json_helper.h index f9cd117e12..82c50c901b 100644 --- a/src/json_helper.h +++ b/src/json_helper.h @@ -25,12 +25,95 @@ #include #include #include +#include + +namespace { +// Helper function to convert JSON values to strings +std::string GetValueAsString(const nlohmann::json& json_obj); +} // namespace namespace Json_Helper { - std::optional Parse(std::string_view json_data); - std::optional GetValue(nlohmann::json& json_obj, std::string_view json_path); - std::string SetValue(nlohmann::json& json_obj, std::string_view json_path, std::string_view value); -} +/** + * Parses a JSON string into a JSON object + * @param json_data The JSON string to parse + * @return The parsed JSON object, or empty if parsing failed + */ +std::optional Parse(std::string_view json_data); + +/** + * Gets a value from a JSON object using a JSON pointer path + * @param json_obj The JSON object to get the value from + * @param json_path The JSON pointer path to the value + * @return The value as a string, or empty if path is invalid + */ +std::optional GetValue(nlohmann::json& json_obj, std::string_view json_path); + +/** + * Sets a value in a JSON object using a JSON pointer path + * @param json_obj The JSON object to modify + * @param json_path The JSON pointer path where to set the value + * @param value The value to set (will be parsed as JSON if valid) + * @return The modified JSON object as a string, or empty string if path is invalid + */ +std::string SetValue(nlohmann::json& json_obj, std::string_view json_path, std::string_view value); + +/** + * Gets the length of an array or object at the specified path + * @param json_obj The JSON object to get the length from + * @param json_path The JSON pointer path to the array/object + * @return The length, or empty if path is invalid or not an array/object + */ +std::optional GetLength(const nlohmann::json& json_obj, std::string_view json_path); + +/** + * Gets all keys from a JSON object or indices from an array at the specified path + * @param json_obj The JSON object to get the keys/indices from + * @param json_path The JSON pointer path to the object or array + * @return Vector of key names (for objects) or indices (for arrays), or empty if path is invalid or not an object/array + */ +std::optional> GetKeys(const nlohmann::json& json_obj, std::string_view json_path); + +/** + * Checks if the value at the path is a JSON object + * @param json_obj The JSON object to check + * @param json_path The JSON pointer path to check + * @return true if object, false if not, empty if path is invalid + */ +std::optional IsObject(const nlohmann::json& json_obj, std::string_view json_path); + +/** + * Checks if the value at the path is a JSON array + * @param json_obj The JSON object to check + * @param json_path The JSON pointer path to check + * @return true if array, false if not, empty if path is invalid + */ +std::optional IsArray(const nlohmann::json& json_obj, std::string_view json_path); + +/** + * Gets the type of value at the specified path + * @param json_obj The JSON object to check + * @param json_path The JSON pointer path to check + * @return The type as a string ("object", "array", "string", "number", "boolean", "null"), or empty if path is invalid + */ +std::optional GetType(const nlohmann::json& json_obj, std::string_view json_path); + +/** + * Gets the full JSON pointer path to a specific value + * @param json_obj The JSON object to search in + * @param search_value The value to search for + * @return The JSON pointer path to the value, or empty if not found + */ +std::optional GetPath(const nlohmann::json& json_obj, const nlohmann::json& search_value); + +/** + * Returns a pretty-printed JSON string with custom indentation + * @param json_obj The JSON object to format + * @param indent Number of spaces for indentation (default: 2) + * @return The formatted JSON string + */ +std::string PrettyPrint(const nlohmann::json& json_obj, int indent = 2); + +} // namespace Json_Helper #endif // HAVE_NLOHMANN_JSON #endif // JSON_HELPER_H