diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 91821c45e0..bc28b71776 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[10]; + bool pretty_print = com.parameters[11] == 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..c984e4be1f 100644 --- a/src/json_helper.cpp +++ b/src/json_helper.cpp @@ -28,73 +28,204 @@ using json = nlohmann::json; namespace { - std::string GetValueAsString(const json& json_obj) { - std::string result; - if (json_obj.is_string()) { - result = json_obj.get(); + std::string GetValueAsString(const json& json_obj) { + if (json_obj.is_null()) { + return "null"; } - else if (json_obj.is_number_integer()) { - result = std::to_string(json_obj.get()); + if (json_obj.is_string()) { + return json_obj.get(); } - else if (json_obj.is_number_float()) { - result = std::to_string(json_obj.get()); + if (json_obj.is_number_integer()) { + return std::to_string(json_obj.get()); } - else if (json_obj.is_boolean()) { - result = json_obj.get() ? "true" : "false"; + if (json_obj.is_number_float()) { + return std::to_string(json_obj.get()); } - else { - result = json_obj.dump(); + if (json_obj.is_boolean()) { + return json_obj.get() ? "true" : "false"; } - - return result; + return json_obj.dump(); } -} + +} // namespace namespace Json_Helper { - std::optional Parse(std::string_view json_data) { + + std::optional Parse(std::string_view json_data) { json json_obj = json::parse(json_data, nullptr, false); if (json_obj.is_discarded()) { - return {}; + return {}; } - return json_obj; } std::optional GetValue(nlohmann::json& json_obj, std::string_view json_path) { - json::json_pointer ptr((std::string(json_path))); - - if (ptr.empty()) { - Output::Warning("JSON: Bad json pointer {}", json_path); + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer at: {}", json_path); return {}; } - if (!json_obj.contains(ptr)) { + std::string path_str = std::string(json_path); + json::json_pointer ptr(path_str); + const json& value = json_obj[ptr]; + if (value.is_null()) { return ""; } - - return GetValueAsString(json_obj[ptr]); + return GetValueAsString(value); } std::string SetValue(nlohmann::json& json_obj, std::string_view json_path, std::string_view value) { - json::json_pointer ptr((std::string(json_path))); - - if (ptr.empty()) { - Output::Warning("JSON: Bad json pointer {}", json_path); + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer at: {}", json_path); return {}; } - json obj_value = json::parse(value, nullptr, false); + std::string path_str = std::string(json_path); + json::json_pointer ptr(path_str); + 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 { + } + else { json_obj[ptr] = obj_value; } return json_obj.dump(); } -} + + std::optional GetLength(const nlohmann::json& json_obj, std::string_view json_path) { + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer at: {}", json_path); + return {}; + } + + std::string path_str = std::string(json_path); + json::json_pointer ptr(path_str); + const json& value = json_obj[ptr]; + if (value.is_null()) { + return 0; + } + + if (!value.is_array() && !value.is_object()) { + return 0; + } + return value.size(); + } + + std::optional> GetKeys(const nlohmann::json& json_obj, std::string_view json_path) { + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer at: {}", json_path); + return {}; + } + + std::string path_str = std::string(json_path); + json::json_pointer ptr(path_str); + const json& value = json_obj[ptr]; + if (value.is_null()) { + return std::vector(); + } + + 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; + } + + std::optional IsObject(const nlohmann::json& json_obj, std::string_view json_path) { + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer at: {}", json_path); + return {}; + } + + std::string path_str = std::string(json_path); + json::json_pointer ptr(path_str); + const json& value = json_obj[ptr]; + if (value.is_null()) { + return false; + } + return value.is_object(); + } + + std::optional IsArray(const nlohmann::json& json_obj, std::string_view json_path) { + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer at: {}", json_path); + return {}; + } + + std::string path_str = std::string(json_path); + json::json_pointer ptr(path_str); + const json& value = json_obj[ptr]; + if (value.is_null()) { + return false; + } + return value.is_array(); + } + + std::optional GetType(const nlohmann::json& json_obj, std::string_view json_path) { + if (json_path.empty()) { + Output::Warning("JSON: Empty json pointer at: {}", json_path); + return {}; + } + + std::string path_str = std::string(json_path); + json::json_pointer ptr(path_str); + const json& value = json_obj[ptr]; + if (value.is_null()) { + return std::string("null"); + } + + 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"); // Should not reach here, but for completeness + return std::string("unknown"); + } + + 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; + }; + + auto path = find_path(json_obj, search_value, ""); + return path.value_or(""); + } + + std::string PrettyPrint(const nlohmann::json& json_obj, int indent) { + return json_obj.dump(std::max(0, indent)); + } + +} // 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