Ability to convert native engine types to JSON and back.

Implements support for all engine types in JSON encoding/decoding

Co-Authored-By: Juan <reduzio@gmail.com>
Co-Authored-By: Rémi Verschelde <rverschelde@gmail.com>
This commit is contained in:
K. S. Ernest (iFire) Lee 2024-06-01 10:40:37 -07:00
parent 5ca419e32c
commit 71bdbcdfb1
5 changed files with 931 additions and 1 deletions

View File

@ -588,10 +588,756 @@ void JSON::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_error_line"), &JSON::get_error_line); 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_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));
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. 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
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::BOOL: {
return p_variant;
} break;
case Variant::INT: {
return p_variant;
} break;
case Variant::FLOAT: {
return 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;
} break;
case Variant::STRING_NAME: {
Dictionary d;
d["name"] = String(p_variant);
d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
return d;
} break;
case Variant::NODE_PATH: {
Dictionary d;
d["path"] = String(p_variant);
d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
return d;
} 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<PropertyInfo> 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::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<Variant> 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));
}
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]);
}
d[VALUES] = values;
d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
return d;
} break;
case Variant::PACKED_INT32_ARRAY: {
Dictionary d;
PackedInt32Array arr = p_variant;
Array values;
for (int i = 0; i < arr.size(); i++) {
values.push_back(arr[i]);
}
d[VALUES] = values;
d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
return d;
} break;
case Variant::PACKED_INT64_ARRAY: {
Dictionary d;
PackedInt64Array arr = p_variant;
Array values;
for (int i = 0; i < arr.size(); i++) {
values.push_back(arr[i]);
}
d[VALUES] = values;
d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
return d;
} break;
case Variant::PACKED_FLOAT32_ARRAY: {
Dictionary d;
PackedFloat32Array arr = p_variant;
Array values;
for (int i = 0; i < arr.size(); i++) {
values.push_back(arr[i]);
}
d[VALUES] = values;
d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
return d;
} break;
case Variant::PACKED_FLOAT64_ARRAY: {
Dictionary d;
PackedFloat64Array arr = p_variant;
Array values;
for (int i = 0; i < arr.size(); i++) {
values.push_back(arr[i]);
}
d[VALUES] = values;
d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
return d;
} break;
case Variant::PACKED_STRING_ARRAY: {
Dictionary d;
PackedStringArray arr = p_variant;
Array values;
for (int i = 0; i < arr.size(); i++) {
values.push_back(arr[i]);
}
d[VALUES] = values;
d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
return d;
} break;
case Variant::PACKED_VECTOR2_ARRAY: {
Dictionary d;
PackedVector2Array arr = p_variant;
Array values;
for (int i = 0; i < arr.size(); i++) {
Vector2 v = arr[i];
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::PACKED_VECTOR3_ARRAY: {
Dictionary d;
PackedVector3Array arr = p_variant;
Array values;
for (int i = 0; i < arr.size(); i++) {
Vector3 v = arr[i];
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::PACKED_COLOR_ARRAY: {
Dictionary d;
PackedColorArray arr = p_variant;
Array values;
for (int i = 0; i < arr.size(); i++) {
Color v = arr[i];
values.push_back(v.r);
values.push_back(v.g);
values.push_back(v.b);
values.push_back(v.a);
}
d[VALUES] = values;
d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
return d;
} break;
case Variant::PACKED_VECTOR4_ARRAY: {
Dictionary d;
PackedVector4Array arr = p_variant;
Array values;
for (int i = 0; i < arr.size(); i++) {
Vector4 v = arr[i];
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;
default: {
ERR_PRINT(vformat("Unhandled conversion from native Variant type '%s' to JSON.", Variant::get_type_name(p_variant.get_type())));
} break;
}
Dictionary nil;
nil[GDTYPE] = Variant::get_type_name(p_variant.get_type());
return nil;
}
Variant JSON::to_native(const Variant &p_json, bool p_allow_classes, bool p_allow_scripts) {
switch (p_json.get_type()) {
case Variant::BOOL: {
return p_json;
} break;
case Variant::INT: {
return p_json;
} break;
case Variant::FLOAT: {
return p_json;
} break;
case Variant::STRING: {
return p_json;
} break;
case Variant::STRING_NAME: {
return p_json;
} break;
case Variant::CALLABLE: {
return p_json;
} break;
case Variant::DICTIONARY: {
Dictionary d = p_json;
if (d.has(GDTYPE)) {
// Specific Godot Variant types serialized to JSON.
String type = d[GDTYPE];
if (type == Variant::get_type_name(Variant::VECTOR2)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
ERR_FAIL_COND_V(values.size() != 2, Variant());
Vector2 v;
v.x = values[0];
v.y = values[1];
return v;
} else if (type == Variant::get_type_name(Variant::VECTOR2I)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
ERR_FAIL_COND_V(values.size() != 2, Variant());
Vector2i v;
v.x = values[0];
v.y = values[1];
return v;
} else if (type == Variant::get_type_name(Variant::RECT2)) {
ERR_FAIL_COND_V(!d.has("position"), Variant());
ERR_FAIL_COND_V(!d.has("size"), Variant());
Rect2 r;
r.position = to_native(d["position"]);
r.size = to_native(d["size"]);
return r;
} else if (type == Variant::get_type_name(Variant::RECT2I)) {
ERR_FAIL_COND_V(!d.has("position"), Variant());
ERR_FAIL_COND_V(!d.has("size"), Variant());
Rect2i r;
r.position = to_native(d["position"]);
r.size = to_native(d["size"]);
return r;
} else if (type == Variant::get_type_name(Variant::VECTOR3)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
ERR_FAIL_COND_V(values.size() != 3, Variant());
Vector3 v;
v.x = values[0];
v.y = values[1];
v.z = values[2];
return v;
} else if (type == Variant::get_type_name(Variant::VECTOR3I)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
ERR_FAIL_COND_V(values.size() != 3, Variant());
Vector3i v;
v.x = values[0];
v.y = values[1];
v.z = values[2];
return v;
} else if (type == Variant::get_type_name(Variant::TRANSFORM2D)) {
ERR_FAIL_COND_V(!d.has("x"), Variant());
ERR_FAIL_COND_V(!d.has("y"), Variant());
ERR_FAIL_COND_V(!d.has("origin"), Variant());
Transform2D t;
t[0] = to_native(d["x"]);
t[1] = to_native(d["y"]);
t[2] = to_native(d["origin"]);
return t;
} else if (type == Variant::get_type_name(Variant::VECTOR4)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
ERR_FAIL_COND_V(values.size() != 4, Variant());
Vector4 v;
v.x = values[0];
v.y = values[1];
v.z = values[2];
v.w = values[3];
return v;
} else if (type == Variant::get_type_name(Variant::VECTOR4I)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
ERR_FAIL_COND_V(values.size() != 4, Variant());
Vector4i v;
v.x = values[0];
v.y = values[1];
v.z = values[2];
v.w = values[3];
return v;
} else if (type == Variant::get_type_name(Variant::PLANE)) {
ERR_FAIL_COND_V(!d.has("normal"), Variant());
ERR_FAIL_COND_V(!d.has("d"), Variant());
Plane p;
p.normal = to_native(d["normal"]);
p.d = d["d"];
return p;
} else if (type == Variant::get_type_name(Variant::QUATERNION)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
ERR_FAIL_COND_V(values.size() != 4, Variant());
Quaternion v;
v.x = values[0];
v.y = values[1];
v.z = values[2];
v.w = values[3];
return v;
} else if (type == Variant::get_type_name(Variant::AABB)) {
ERR_FAIL_COND_V(!d.has("position"), Variant());
ERR_FAIL_COND_V(!d.has("size"), Variant());
AABB r;
r.position = to_native(d["position"]);
r.size = to_native(d["size"]);
return r;
} else if (type == Variant::get_type_name(Variant::BASIS)) {
ERR_FAIL_COND_V(!d.has("x"), Variant());
ERR_FAIL_COND_V(!d.has("y"), Variant());
ERR_FAIL_COND_V(!d.has("z"), Variant());
Basis b;
b.set_column(0, to_native(d["x"]));
b.set_column(1, to_native(d["y"]));
b.set_column(2, to_native(d["z"]));
return b;
} else if (type == Variant::get_type_name(Variant::TRANSFORM3D)) {
ERR_FAIL_COND_V(!d.has("basis"), Variant());
ERR_FAIL_COND_V(!d.has("origin"), Variant());
Transform3D t;
t.basis = to_native(d["basis"]);
t.origin = to_native(d["origin"]);
return t;
} else if (type == Variant::get_type_name(Variant::PROJECTION)) {
ERR_FAIL_COND_V(!d.has("x"), Variant());
ERR_FAIL_COND_V(!d.has("y"), Variant());
ERR_FAIL_COND_V(!d.has("z"), Variant());
ERR_FAIL_COND_V(!d.has("w"), Variant());
Projection p;
p[0] = to_native(d["x"]);
p[1] = to_native(d["y"]);
p[2] = to_native(d["z"]);
p[3] = to_native(d["w"]);
return p;
} else if (type == Variant::get_type_name(Variant::COLOR)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
ERR_FAIL_COND_V(values.size() != 4, Variant());
Color c;
c.r = values[0];
c.g = values[1];
c.b = values[2];
c.a = values[3];
return c;
} else if (type == Variant::get_type_name(Variant::NODE_PATH)) {
ERR_FAIL_COND_V(!d.has("path"), Variant());
NodePath np = d["path"];
return np;
} else if (type == Variant::get_type_name(Variant::STRING_NAME)) {
ERR_FAIL_COND_V(!d.has("name"), Variant());
StringName s = d["name"];
return s;
} else if (type == Variant::get_type_name(Variant::OBJECT)) {
ERR_FAIL_COND_V(!d.has("type"), Variant());
ERR_FAIL_COND_V(!d.has("properties"), Variant());
ERR_FAIL_COND_V(!p_allow_classes, Variant());
String obj_type = d["type"];
bool is_script = obj_type == "Script" || ClassDB::is_parent_class(obj_type, "Script");
ERR_FAIL_COND_V(!p_allow_scripts && is_script, Variant());
Object *obj = ClassDB::instantiate(obj_type);
ERR_FAIL_NULL_V(obj, Variant());
Dictionary p = d["properties"];
List<Variant> keys;
p.get_key_list(&keys);
for (const Variant &K : keys) {
String property = K;
Variant value = to_native(p[K], PASS_ARG);
obj->set(property, value);
}
Variant v(obj);
return v;
} else if (type == Variant::get_type_name(Variant::DICTIONARY)) {
ERR_FAIL_COND_V(!d.has("pairs"), Variant());
Array pairs = d["pairs"];
Dictionary r;
for (int i = 0; i < pairs.size(); i++) {
Dictionary p = pairs[i];
ERR_CONTINUE(!p.has("key"));
ERR_CONTINUE(!p.has("value"));
r[to_native(p["key"], PASS_ARG)] = to_native(p["value"]);
}
return r;
} else if (type == Variant::get_type_name(Variant::ARRAY)) {
ERR_PRINT(vformat("Unexpected Array with '%s' key. Arrays are supported natively.", GDTYPE));
} else if (type == Variant::get_type_name(Variant::PACKED_BYTE_ARRAY)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
PackedByteArray pbarr;
pbarr.resize(values.size());
for (int i = 0; i < pbarr.size(); i++) {
pbarr.write[i] = values[i];
}
return pbarr;
} else if (type == Variant::get_type_name(Variant::PACKED_INT32_ARRAY)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
PackedInt32Array arr;
arr.resize(values.size());
for (int i = 0; i < arr.size(); i++) {
arr.write[i] = values[i];
}
return arr;
} else if (type == Variant::get_type_name(Variant::PACKED_INT64_ARRAY)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
PackedInt64Array arr;
arr.resize(values.size());
for (int i = 0; i < arr.size(); i++) {
arr.write[i] = values[i];
}
return arr;
} else if (type == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
PackedFloat32Array arr;
arr.resize(values.size());
for (int i = 0; i < arr.size(); i++) {
arr.write[i] = values[i];
}
return arr;
} else if (type == Variant::get_type_name(Variant::PACKED_FLOAT64_ARRAY)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
PackedFloat64Array arr;
arr.resize(values.size());
for (int i = 0; i < arr.size(); i++) {
arr.write[i] = values[i];
}
return arr;
} else if (type == Variant::get_type_name(Variant::PACKED_STRING_ARRAY)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
PackedStringArray arr;
arr.resize(values.size());
for (int i = 0; i < arr.size(); i++) {
arr.write[i] = values[i];
}
return arr;
} else if (type == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
ERR_FAIL_COND_V(values.size() % 2 != 0, Variant());
PackedVector2Array arr;
arr.resize(values.size() / 2);
for (int i = 0; i < arr.size(); i++) {
arr.write[i] = Vector2(values[i * 2 + 0], values[i * 2 + 1]);
}
return arr;
} else if (type == Variant::get_type_name(Variant::PACKED_VECTOR3_ARRAY)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
ERR_FAIL_COND_V(values.size() % 3 != 0, Variant());
PackedVector3Array arr;
arr.resize(values.size() / 3);
for (int i = 0; i < arr.size(); i++) {
arr.write[i] = Vector3(values[i * 3 + 0], values[i * 3 + 1], values[i * 3 + 2]);
}
return arr;
} else if (type == Variant::get_type_name(Variant::PACKED_COLOR_ARRAY)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
ERR_FAIL_COND_V(values.size() % 4 != 0, Variant());
PackedColorArray arr;
arr.resize(values.size() / 4);
for (int i = 0; i < arr.size(); i++) {
arr.write[i] = Color(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]);
}
return arr;
} else if (type == Variant::get_type_name(Variant::PACKED_VECTOR4_ARRAY)) {
ERR_FAIL_COND_V(!d.has(VALUES), Variant());
Array values = d[VALUES];
ERR_FAIL_COND_V(values.size() % 4 != 0, Variant());
PackedVector4Array arr;
arr.resize(values.size() / 4);
for (int i = 0; i < arr.size(); i++) {
arr.write[i] = Vector4(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]);
}
return arr;
} else {
return Variant();
}
} else {
// Regular dictionary with string keys.
List<Variant> keys;
d.get_key_list(&keys);
Dictionary r;
for (const Variant &K : keys) {
r[K] = to_native(d[K], PASS_ARG);
}
return r;
}
} break;
case Variant::ARRAY: {
Array arr = p_json;
Array ret;
ret.resize(arr.size());
for (int i = 0; i < arr.size(); i++) {
ret[i] = to_native(arr[i], PASS_ARG);
}
return ret;
} break;
default: {
ERR_PRINT(vformat("Unhandled conversion from JSON type '%s' to native Variant type.", Variant::get_type_name(p_json.get_type())));
return Variant();
}
}
return Variant();
}
#undef GDTYPE
#undef VALUES
#undef PASS_ARG
//////////// ////////////

View File

@ -94,6 +94,9 @@ public:
void set_data(const Variant &p_data); void set_data(const Variant &p_data);
inline int get_error_line() const { return err_line; } inline int get_error_line() const { return err_line; }
inline String get_error_message() const { return err_str; } inline String get_error_message() const { return err_str; }
static Variant from_native(const Variant &p_variant, bool p_allow_classes = false, bool p_allow_scripts = false);
static Variant to_native(const Variant &p_json, bool p_allow_classes = false, bool p_allow_scripts = false);
}; };
class ResourceFormatLoaderJSON : public ResourceFormatLoader { class ResourceFormatLoaderJSON : public ResourceFormatLoader {

View File

@ -38,6 +38,16 @@
<tutorials> <tutorials>
</tutorials> </tutorials>
<methods> <methods>
<method name="from_native" qualifiers="static">
<return type="Variant" />
<param index="0" name="variant" type="Variant" />
<param index="1" name="allow_classes" type="bool" default="false" />
<param index="2" name="allow_scripts" type="bool" default="false" />
<description>
Converts a native engine type to a JSON-compliant dictionary.
By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified.
</description>
</method>
<method name="get_error_line" qualifiers="const"> <method name="get_error_line" qualifiers="const">
<return type="int" /> <return type="int" />
<description> <description>
@ -124,6 +134,16 @@
[/codeblock] [/codeblock]
</description> </description>
</method> </method>
<method name="to_native" qualifiers="static">
<return type="Variant" />
<param index="0" name="json" type="Variant" />
<param index="1" name="allow_classes" type="bool" default="false" />
<param index="2" name="allow_scripts" type="bool" default="false" />
<description>
Converts a JSON-compliant dictionary that was created with [method from_native] back to native engine types.
By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified.
</description>
</method>
</methods> </methods>
<members> <members>
<member name="data" type="Variant" setter="set_data" getter="get_data" default="null"> <member name="data" type="Variant" setter="set_data" getter="get_data" default="null">

View File

@ -0,0 +1,160 @@
/**************************************************************************/
/* test_json_native.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TEST_JSON_NATIVE_H
#define TEST_JSON_NATIVE_H
#include "core/io/json.h"
namespace TestJSONNative {
bool compare_variants(Variant variant_1, Variant variant_2, int depth = 0) {
if (depth > 100) {
return false;
}
if (variant_1.get_type() == Variant::RID && variant_2.get_type() == Variant::RID) {
return true;
}
if (variant_1.get_type() == Variant::CALLABLE || variant_2.get_type() == Variant::CALLABLE) {
return true;
}
List<PropertyInfo> variant_1_properties;
variant_1.get_property_list(&variant_1_properties);
List<PropertyInfo> variant_2_properties;
variant_2.get_property_list(&variant_2_properties);
if (variant_1_properties.size() != variant_2_properties.size()) {
return false;
}
for (List<PropertyInfo>::Element *E = variant_1_properties.front(); E; E = E->next()) {
String name = E->get().name;
Variant variant_1_value = variant_1.get(name);
Variant variant_2_value = variant_2.get(name);
if (!compare_variants(variant_1_value, variant_2_value, depth + 1)) {
return false;
}
}
return true;
}
TEST_CASE("[JSON][Native][SceneTree] Conversion between native and JSON formats") {
for (int variant_i = 0; variant_i < Variant::VARIANT_MAX; variant_i++) {
Variant::Type type = static_cast<Variant::Type>(variant_i);
Variant native_data;
Callable::CallError error;
if (type == Variant::Type::INT || type == Variant::Type::FLOAT) {
Variant value = int64_t(INT64_MAX);
const Variant *args[] = { &value };
Variant::construct(type, native_data, args, 1, error);
} else if (type == Variant::Type::OBJECT) {
Ref<JSON> json = memnew(JSON);
native_data = json;
} else if (type == Variant::Type::DICTIONARY) {
Dictionary dictionary;
dictionary["key"] = "value";
native_data = dictionary;
} else if (type == Variant::Type::ARRAY) {
Array array;
array.push_back("element1");
array.push_back("element2");
native_data = array;
} else if (type == Variant::Type::PACKED_BYTE_ARRAY) {
PackedByteArray packed_array;
packed_array.push_back(1);
packed_array.push_back(2);
native_data = packed_array;
} else if (type == Variant::Type::PACKED_INT32_ARRAY) {
PackedInt32Array packed_array;
packed_array.push_back(INT32_MIN);
packed_array.push_back(INT32_MAX);
native_data = packed_array;
} else if (type == Variant::Type::PACKED_INT64_ARRAY) {
PackedInt64Array packed_array;
packed_array.push_back(INT64_MIN);
packed_array.push_back(INT64_MAX);
native_data = packed_array;
} else if (type == Variant::Type::PACKED_FLOAT32_ARRAY) {
PackedFloat32Array packed_array;
packed_array.push_back(FLT_MIN);
packed_array.push_back(FLT_MAX);
native_data = packed_array;
} else if (type == Variant::Type::PACKED_FLOAT64_ARRAY) {
PackedFloat64Array packed_array;
packed_array.push_back(DBL_MIN);
packed_array.push_back(DBL_MAX);
native_data = packed_array;
} else if (type == Variant::Type::PACKED_STRING_ARRAY) {
PackedStringArray packed_array;
packed_array.push_back("string1");
packed_array.push_back("string2");
native_data = packed_array;
} else if (type == Variant::Type::PACKED_VECTOR2_ARRAY) {
PackedVector2Array packed_array;
Vector2 vector(1.0, 2.0);
packed_array.push_back(vector);
native_data = packed_array;
} else if (type == Variant::Type::PACKED_VECTOR3_ARRAY) {
PackedVector3Array packed_array;
Vector3 vector(1.0, 2.0, 3.0);
packed_array.push_back(vector);
native_data = packed_array;
} else if (type == Variant::Type::PACKED_COLOR_ARRAY) {
PackedColorArray packed_array;
Color color(1.0, 1.0, 1.0);
packed_array.push_back(color);
native_data = packed_array;
} else if (type == Variant::Type::PACKED_VECTOR4_ARRAY) {
PackedVector4Array packed_array;
Vector4 vector(1.0, 2.0, 3.0, 4.0);
packed_array.push_back(vector);
native_data = packed_array;
} else {
Variant::construct(type, native_data, nullptr, 0, error);
}
Variant json_converted_from_native = JSON::from_native(native_data, true, true);
Variant variant_native_converted = JSON::to_native(json_converted_from_native, true, true);
CHECK_MESSAGE(compare_variants(native_data, variant_native_converted),
vformat("Conversion from native to JSON type %s and back successful. \nNative: %s \nNative Converted: %s \nError: %s\nConversion from native to JSON type %s successful: %s",
Variant::get_type_name(type),
native_data,
variant_native_converted,
itos(error.error),
Variant::get_type_name(type),
json_converted_from_native));
}
}
} // namespace TestJSONNative
#endif // TEST_JSON_NATIVE_H

View File

@ -48,6 +48,7 @@
#include "tests/core/io/test_image.h" #include "tests/core/io/test_image.h"
#include "tests/core/io/test_ip.h" #include "tests/core/io/test_ip.h"
#include "tests/core/io/test_json.h" #include "tests/core/io/test_json.h"
#include "tests/core/io/test_json_native.h"
#include "tests/core/io/test_marshalls.h" #include "tests/core/io/test_marshalls.h"
#include "tests/core/io/test_pck_packer.h" #include "tests/core/io/test_pck_packer.h"
#include "tests/core/io/test_resource.h" #include "tests/core/io/test_resource.h"