From bd1a35ce9e6f7e6e4c87fa28278ed54e8f84ff3f Mon Sep 17 00:00:00 2001 From: Danil Alexeev Date: Wed, 27 Nov 2024 18:15:49 +0300 Subject: [PATCH] Core: Fix `JSON.{from,to}_native()` issues --- core/io/json.cpp | 1598 ++++++++++++++---------- core/io/json.h | 20 +- core/io/marshalls.cpp | 66 +- core/math/expression.cpp | 7 +- core/variant/array.cpp | 15 +- core/variant/array.h | 12 +- core/variant/container_type_validate.h | 6 + core/variant/dictionary.cpp | 20 + core/variant/dictionary.h | 6 + core/variant/variant.cpp | 12 + core/variant/variant.h | 1 + doc/classes/JSON.xml | 24 +- editor/editor_autoload_settings.cpp | 12 +- modules/gdscript/gdscript_editor.cpp | 11 +- tests/core/io/test_json_native.h | 301 +++-- 15 files changed, 1237 insertions(+), 874 deletions(-) diff --git a/core/io/json.cpp b/core/io/json.cpp index e73677be9ca..b6b1a88479b 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -31,7 +31,8 @@ #include "json.h" #include "core/config/engine.h" -#include "core/string/print_string.h" +#include "core/object/script_language.h" +#include "core/variant/container_type_validate.h" const char *JSON::tk_name[TK_MAX] = { "'{'", @@ -563,18 +564,18 @@ String JSON::get_parsed_text() const { } String JSON::stringify(const Variant &p_var, const String &p_indent, bool p_sort_keys, bool p_full_precision) { - Ref jason; - jason.instantiate(); + Ref json; + json.instantiate(); HashSet markers; - return jason->_stringify(p_var, p_indent, 0, p_sort_keys, markers, p_full_precision); + return json->_stringify(p_var, p_indent, 0, p_sort_keys, markers, p_full_precision); } Variant JSON::parse_string(const String &p_json_string) { - Ref jason; - jason.instantiate(); - Error error = jason->parse(p_json_string); - ERR_FAIL_COND_V_MSG(error != Error::OK, Variant(), vformat("Parse JSON failed. Error at line %d: %s", jason->get_error_line(), jason->get_error_message())); - return jason->get_data(); + Ref json; + json.instantiate(); + Error error = json->parse(p_json_string); + ERR_FAIL_COND_V_MSG(error != Error::OK, Variant(), vformat("Parse JSON failed. Error at line %d: %s", json->get_error_line(), json->get_error_message())); + return json->get_data(); } void JSON::_bind_methods() { @@ -588,756 +589,1015 @@ void JSON::_bind_methods() { ClassDB::bind_method(D_METHOD("get_error_line"), &JSON::get_error_line); ClassDB::bind_method(D_METHOD("get_error_message"), &JSON::get_error_message); - ClassDB::bind_static_method("JSON", D_METHOD("to_native", "json", "allow_classes", "allow_scripts"), &JSON::to_native, DEFVAL(false), DEFVAL(false)); - ClassDB::bind_static_method("JSON", D_METHOD("from_native", "variant", "allow_classes", "allow_scripts"), &JSON::from_native, DEFVAL(false), DEFVAL(false)); + ClassDB::bind_static_method("JSON", D_METHOD("from_native", "variant", "full_objects"), &JSON::from_native, DEFVAL(false)); + ClassDB::bind_static_method("JSON", D_METHOD("to_native", "json", "allow_objects"), &JSON::to_native, DEFVAL(false)); ADD_PROPERTY(PropertyInfo(Variant::NIL, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), "set_data", "get_data"); // Ensures that it can be serialized as binary. } -#define GDTYPE "__gdtype" -#define VALUES "values" -#define PASS_ARG p_allow_classes, p_allow_scripts +#define TYPE "type" +#define ELEM_TYPE "elem_type" +#define KEY_TYPE "key_type" +#define VALUE_TYPE "value_type" +#define ARGS "args" +#define PROPS "props" + +static bool _encode_container_type(Dictionary &r_dict, const String &p_key, const ContainerType &p_type, bool p_full_objects) { + if (p_type.builtin_type != Variant::NIL) { + if (p_type.script.is_valid()) { + ERR_FAIL_COND_V(!p_full_objects, false); + const String path = p_type.script->get_path(); + ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), false, "Failed to encode a path to a custom script for a container type."); + r_dict[p_key] = path; + } else if (p_type.class_name != StringName()) { + ERR_FAIL_COND_V(!p_full_objects, false); + r_dict[p_key] = String(p_type.class_name); + } else { + // No need to check `p_full_objects` since `class_name` should be non-empty for `builtin_type == Variant::OBJECT`. + r_dict[p_key] = Variant::get_type_name(p_type.builtin_type); + } + } + return true; +} + +Variant JSON::_from_native(const Variant &p_variant, bool p_full_objects, int p_depth) { +#define RETURN_ARGS \ + Dictionary ret; \ + ret[TYPE] = Variant::get_type_name(p_variant.get_type()); \ + ret[ARGS] = args; \ + return ret -Variant JSON::from_native(const Variant &p_variant, bool p_allow_classes, bool p_allow_scripts) { switch (p_variant.get_type()) { - case Variant::NIL: { - Dictionary nil; - nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return nil; - } break; + case Variant::NIL: case Variant::BOOL: { return p_variant; } break; + case Variant::INT: { - return p_variant; + return "i:" + String(p_variant); } break; case Variant::FLOAT: { - return p_variant; + return "f:" + String(p_variant); } break; case Variant::STRING: { - return p_variant; - } break; - case Variant::VECTOR2: { - Dictionary d; - Vector2 v = p_variant; - Array values; - values.push_back(v.x); - values.push_back(v.y); - d[VALUES] = values; - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::VECTOR2I: { - Dictionary d; - Vector2i v = p_variant; - Array values; - values.push_back(v.x); - values.push_back(v.y); - d[VALUES] = values; - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::RECT2: { - Dictionary d; - Rect2 r = p_variant; - d["position"] = from_native(r.position); - d["size"] = from_native(r.size); - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::RECT2I: { - Dictionary d; - Rect2i r = p_variant; - d["position"] = from_native(r.position); - d["size"] = from_native(r.size); - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::VECTOR3: { - Dictionary d; - Vector3 v = p_variant; - Array values; - values.push_back(v.x); - values.push_back(v.y); - values.push_back(v.z); - d[VALUES] = values; - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::VECTOR3I: { - Dictionary d; - Vector3i v = p_variant; - Array values; - values.push_back(v.x); - values.push_back(v.y); - values.push_back(v.z); - d[VALUES] = values; - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::TRANSFORM2D: { - Dictionary d; - Transform2D t = p_variant; - d["x"] = from_native(t[0]); - d["y"] = from_native(t[1]); - d["origin"] = from_native(t[2]); - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::VECTOR4: { - Dictionary d; - Vector4 v = p_variant; - Array values; - values.push_back(v.x); - values.push_back(v.y); - values.push_back(v.z); - values.push_back(v.w); - d[VALUES] = values; - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::VECTOR4I: { - Dictionary d; - Vector4i v = p_variant; - Array values; - values.push_back(v.x); - values.push_back(v.y); - values.push_back(v.z); - values.push_back(v.w); - d[VALUES] = values; - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::PLANE: { - Dictionary d; - Plane p = p_variant; - d["normal"] = from_native(p.normal); - d["d"] = p.d; - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::QUATERNION: { - Dictionary d; - Quaternion q = p_variant; - Array values; - values.push_back(q.x); - values.push_back(q.y); - values.push_back(q.z); - values.push_back(q.w); - d[VALUES] = values; - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::AABB: { - Dictionary d; - AABB aabb = p_variant; - d["position"] = from_native(aabb.position); - d["size"] = from_native(aabb.size); - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::BASIS: { - Dictionary d; - Basis t = p_variant; - d["x"] = from_native(t.get_column(0)); - d["y"] = from_native(t.get_column(1)); - d["z"] = from_native(t.get_column(2)); - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::TRANSFORM3D: { - Dictionary d; - Transform3D t = p_variant; - d["basis"] = from_native(t.basis); - d["origin"] = from_native(t.origin); - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::PROJECTION: { - Dictionary d; - Projection t = p_variant; - d["x"] = from_native(t[0]); - d["y"] = from_native(t[1]); - d["z"] = from_native(t[2]); - d["w"] = from_native(t[3]); - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::COLOR: { - Dictionary d; - Color c = p_variant; - Array values; - values.push_back(c.r); - values.push_back(c.g); - values.push_back(c.b); - values.push_back(c.a); - d[VALUES] = values; - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; + return "s:" + String(p_variant); } break; case Variant::STRING_NAME: { - Dictionary d; - d["name"] = String(p_variant); - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; + return "sn:" + String(p_variant); } break; case Variant::NODE_PATH: { - Dictionary d; - d["path"] = String(p_variant); - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; + return "np:" + String(p_variant); } break; - case Variant::RID: { - Dictionary d; - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } break; - case Variant::OBJECT: { - Object *obj = p_variant.get_validated_object(); - if (p_allow_classes && obj) { - Dictionary d; - List property_list; - obj->get_property_list(&property_list); - - d["type"] = obj->get_class(); - Dictionary p; - for (const PropertyInfo &P : property_list) { - if (P.usage & PROPERTY_USAGE_STORAGE) { - if (P.name == "script" && !p_allow_scripts) { - continue; - } - p[P.name] = from_native(obj->get(P.name), PASS_ARG); - } - } - d["properties"] = p; - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; - } else { - Dictionary nil; - nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return nil; - } - } break; + case Variant::RID: case Variant::CALLABLE: case Variant::SIGNAL: { - Dictionary nil; - nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return nil; - } break; - case Variant::DICTIONARY: { - Dictionary d = p_variant; - List keys; - d.get_key_list(&keys); - bool all_strings = true; - for (const Variant &K : keys) { - if (K.get_type() != Variant::STRING) { - all_strings = false; - break; - } - } - - if (all_strings) { - Dictionary ret_dict; - for (const Variant &K : keys) { - ret_dict[K] = from_native(d[K], PASS_ARG); - } - return ret_dict; - } else { - Dictionary ret; - Array pairs; - for (const Variant &K : keys) { - Dictionary pair; - pair["key"] = from_native(K, PASS_ARG); - pair["value"] = from_native(d[K], PASS_ARG); - pairs.push_back(pair); - } - ret["pairs"] = pairs; - ret[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return ret; - } - } break; - case Variant::ARRAY: { - Array arr = p_variant; - Array ret; - for (int i = 0; i < arr.size(); i++) { - ret.push_back(from_native(arr[i], PASS_ARG)); - } + Dictionary ret; + ret[TYPE] = Variant::get_type_name(p_variant.get_type()); return ret; } break; - case Variant::PACKED_BYTE_ARRAY: { - Dictionary d; - PackedByteArray arr = p_variant; - Array values; - for (int i = 0; i < arr.size(); i++) { - values.push_back(arr[i]); + + case Variant::VECTOR2: { + const Vector2 v = p_variant; + + Array args; + args.push_back(v.x); + args.push_back(v.y); + + RETURN_ARGS; + } break; + case Variant::VECTOR2I: { + const Vector2i v = p_variant; + + Array args; + args.push_back(v.x); + args.push_back(v.y); + + RETURN_ARGS; + } break; + case Variant::RECT2: { + const Rect2 r = p_variant; + + Array args; + args.push_back(r.position.x); + args.push_back(r.position.y); + args.push_back(r.size.width); + args.push_back(r.size.height); + + RETURN_ARGS; + } break; + case Variant::RECT2I: { + const Rect2i r = p_variant; + + Array args; + args.push_back(r.position.x); + args.push_back(r.position.y); + args.push_back(r.size.width); + args.push_back(r.size.height); + + RETURN_ARGS; + } break; + case Variant::VECTOR3: { + const Vector3 v = p_variant; + + Array args; + args.push_back(v.x); + args.push_back(v.y); + args.push_back(v.z); + + RETURN_ARGS; + } break; + case Variant::VECTOR3I: { + const Vector3i v = p_variant; + + Array args; + args.push_back(v.x); + args.push_back(v.y); + args.push_back(v.z); + + RETURN_ARGS; + } break; + case Variant::TRANSFORM2D: { + const Transform2D t = p_variant; + + Array args; + args.push_back(t[0].x); + args.push_back(t[0].y); + args.push_back(t[1].x); + args.push_back(t[1].y); + args.push_back(t[2].x); + args.push_back(t[2].y); + + RETURN_ARGS; + } break; + case Variant::VECTOR4: { + const Vector4 v = p_variant; + + Array args; + args.push_back(v.x); + args.push_back(v.y); + args.push_back(v.z); + args.push_back(v.w); + + RETURN_ARGS; + } break; + case Variant::VECTOR4I: { + const Vector4i v = p_variant; + + Array args; + args.push_back(v.x); + args.push_back(v.y); + args.push_back(v.z); + args.push_back(v.w); + + RETURN_ARGS; + } break; + case Variant::PLANE: { + const Plane p = p_variant; + + Array args; + args.push_back(p.normal.x); + args.push_back(p.normal.y); + args.push_back(p.normal.z); + args.push_back(p.d); + + RETURN_ARGS; + } break; + case Variant::QUATERNION: { + const Quaternion q = p_variant; + + Array args; + args.push_back(q.x); + args.push_back(q.y); + args.push_back(q.z); + args.push_back(q.w); + + RETURN_ARGS; + } break; + case Variant::AABB: { + const AABB aabb = p_variant; + + Array args; + args.push_back(aabb.position.x); + args.push_back(aabb.position.y); + args.push_back(aabb.position.z); + args.push_back(aabb.size.x); + args.push_back(aabb.size.y); + args.push_back(aabb.size.z); + + RETURN_ARGS; + } break; + case Variant::BASIS: { + const Basis b = p_variant; + + Array args; + args.push_back(b.get_column(0).x); + args.push_back(b.get_column(0).y); + args.push_back(b.get_column(0).z); + args.push_back(b.get_column(1).x); + args.push_back(b.get_column(1).y); + args.push_back(b.get_column(1).z); + args.push_back(b.get_column(2).x); + args.push_back(b.get_column(2).y); + args.push_back(b.get_column(2).z); + + RETURN_ARGS; + } break; + case Variant::TRANSFORM3D: { + const Transform3D t = p_variant; + + Array args; + args.push_back(t.basis.get_column(0).x); + args.push_back(t.basis.get_column(0).y); + args.push_back(t.basis.get_column(0).z); + args.push_back(t.basis.get_column(1).x); + args.push_back(t.basis.get_column(1).y); + args.push_back(t.basis.get_column(1).z); + args.push_back(t.basis.get_column(2).x); + args.push_back(t.basis.get_column(2).y); + args.push_back(t.basis.get_column(2).z); + args.push_back(t.origin.x); + args.push_back(t.origin.y); + args.push_back(t.origin.z); + + RETURN_ARGS; + } break; + case Variant::PROJECTION: { + const Projection p = p_variant; + + Array args; + args.push_back(p[0].x); + args.push_back(p[0].y); + args.push_back(p[0].z); + args.push_back(p[0].w); + args.push_back(p[1].x); + args.push_back(p[1].y); + args.push_back(p[1].z); + args.push_back(p[1].w); + args.push_back(p[2].x); + args.push_back(p[2].y); + args.push_back(p[2].z); + args.push_back(p[2].w); + args.push_back(p[3].x); + args.push_back(p[3].y); + args.push_back(p[3].z); + args.push_back(p[3].w); + + RETURN_ARGS; + } break; + case Variant::COLOR: { + const Color c = p_variant; + + Array args; + args.push_back(c.r); + args.push_back(c.g); + args.push_back(c.b); + args.push_back(c.a); + + RETURN_ARGS; + } break; + + case Variant::OBJECT: { + ERR_FAIL_COND_V(!p_full_objects, Variant()); + + ERR_FAIL_COND_V_MSG(p_depth > Variant::MAX_RECURSION_DEPTH, Variant(), "Variant is too deep. Bailing."); + + const Object *obj = p_variant.get_validated_object(); + if (obj == nullptr) { + return Variant(); } - d[VALUES] = values; - d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); - return d; + + ERR_FAIL_COND_V(!ClassDB::can_instantiate(obj->get_class()), Variant()); + + List prop_list; + obj->get_property_list(&prop_list); + + Array props; + for (const PropertyInfo &pi : prop_list) { + if (!(pi.usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + + Variant value; + if (pi.name == CoreStringName(script)) { + const Ref