You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-10 13:00:37 +00:00
Core: Fix JSON.{from,to}_native() issues
This commit is contained in:
1500
core/io/json.cpp
1500
core/io/json.cpp
File diff suppressed because it is too large
Load Diff
@@ -80,6 +80,9 @@ class JSON : public Resource {
|
||||
static Error _parse_object(Dictionary &object, const char32_t *p_str, int &index, int p_len, int &line, int p_depth, String &r_err_str);
|
||||
static Error _parse_string(const String &p_json, Variant &r_ret, String &r_err_str, int &r_err_line);
|
||||
|
||||
static Variant _from_native(const Variant &p_variant, bool p_full_objects, int p_depth);
|
||||
static Variant _to_native(const Variant &p_json, bool p_allow_objects, int p_depth);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
@@ -90,13 +93,18 @@ public:
|
||||
static String stringify(const Variant &p_var, const String &p_indent = "", bool p_sort_keys = true, bool p_full_precision = false);
|
||||
static Variant parse_string(const String &p_json_string);
|
||||
|
||||
inline Variant get_data() const { return data; }
|
||||
void set_data(const Variant &p_data);
|
||||
inline int get_error_line() const { return err_line; }
|
||||
inline String get_error_message() const { return err_str; }
|
||||
_FORCE_INLINE_ static Variant from_native(const Variant &p_variant, bool p_full_objects = false) {
|
||||
return _from_native(p_variant, p_full_objects, 0);
|
||||
}
|
||||
_FORCE_INLINE_ static Variant to_native(const Variant &p_json, bool p_allow_objects = false) {
|
||||
return _to_native(p_json, p_allow_objects, 0);
|
||||
}
|
||||
|
||||
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);
|
||||
void set_data(const Variant &p_data);
|
||||
_FORCE_INLINE_ Variant get_data() const { return data; }
|
||||
|
||||
_FORCE_INLINE_ int get_error_line() const { return err_line; }
|
||||
_FORCE_INLINE_ String get_error_message() const { return err_str; }
|
||||
};
|
||||
|
||||
class ResourceFormatLoaderJSON : public ResourceFormatLoader {
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/variant/container_type_validate.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
@@ -84,12 +85,6 @@ enum ContainerTypeKind {
|
||||
CONTAINER_TYPE_KIND_SCRIPT = 0b11,
|
||||
};
|
||||
|
||||
struct ContainerType {
|
||||
Variant::Type builtin_type = Variant::NIL;
|
||||
StringName class_name;
|
||||
Ref<Script> script;
|
||||
};
|
||||
|
||||
#define GET_CONTAINER_TYPE_KIND(m_header, m_field) \
|
||||
((ContainerTypeKind)(((m_header) & HEADER_DATA_FIELD_##m_field##_MASK) >> HEADER_DATA_FIELD_##m_field##_SHIFT))
|
||||
|
||||
@@ -844,7 +839,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
|
||||
|
||||
Dictionary dict;
|
||||
if (key_type.builtin_type != Variant::NIL || value_type.builtin_type != Variant::NIL) {
|
||||
dict.set_typed(key_type.builtin_type, key_type.class_name, key_type.script, value_type.builtin_type, value_type.class_name, value_type.script);
|
||||
dict.set_typed(key_type, value_type);
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -901,7 +896,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
|
||||
|
||||
Array array;
|
||||
if (type.builtin_type != Variant::NIL) {
|
||||
array.set_typed(type.builtin_type, type.class_name, type.script);
|
||||
array.set_typed(type);
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -1402,31 +1397,13 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
|
||||
}
|
||||
} break;
|
||||
case Variant::DICTIONARY: {
|
||||
Dictionary dict = p_variant;
|
||||
|
||||
ContainerType key_type;
|
||||
key_type.builtin_type = (Variant::Type)dict.get_typed_key_builtin();
|
||||
key_type.class_name = dict.get_typed_key_class_name();
|
||||
key_type.script = dict.get_typed_key_script();
|
||||
|
||||
_encode_container_type_header(key_type, header, HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_SHIFT, p_full_objects);
|
||||
|
||||
ContainerType value_type;
|
||||
value_type.builtin_type = (Variant::Type)dict.get_typed_value_builtin();
|
||||
value_type.class_name = dict.get_typed_value_class_name();
|
||||
value_type.script = dict.get_typed_value_script();
|
||||
|
||||
_encode_container_type_header(value_type, header, HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_SHIFT, p_full_objects);
|
||||
const Dictionary dict = p_variant;
|
||||
_encode_container_type_header(dict.get_key_type(), header, HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_SHIFT, p_full_objects);
|
||||
_encode_container_type_header(dict.get_value_type(), header, HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_SHIFT, p_full_objects);
|
||||
} break;
|
||||
case Variant::ARRAY: {
|
||||
Array array = p_variant;
|
||||
|
||||
ContainerType type;
|
||||
type.builtin_type = (Variant::Type)array.get_typed_builtin();
|
||||
type.class_name = array.get_typed_class_name();
|
||||
type.script = array.get_typed_script();
|
||||
|
||||
_encode_container_type_header(type, header, HEADER_DATA_FIELD_TYPED_ARRAY_SHIFT, p_full_objects);
|
||||
const Array array = p_variant;
|
||||
_encode_container_type_header(array.get_element_type(), header, HEADER_DATA_FIELD_TYPED_ARRAY_SHIFT, p_full_objects);
|
||||
} break;
|
||||
#ifdef REAL_T_IS_DOUBLE
|
||||
case Variant::VECTOR2:
|
||||
@@ -1850,27 +1827,17 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
|
||||
r_len += 8;
|
||||
} break;
|
||||
case Variant::DICTIONARY: {
|
||||
Dictionary dict = p_variant;
|
||||
const Dictionary dict = p_variant;
|
||||
|
||||
{
|
||||
ContainerType key_type;
|
||||
key_type.builtin_type = (Variant::Type)dict.get_typed_key_builtin();
|
||||
key_type.class_name = dict.get_typed_key_class_name();
|
||||
key_type.script = dict.get_typed_key_script();
|
||||
|
||||
Error err = _encode_container_type(key_type, buf, r_len, p_full_objects);
|
||||
Error err = _encode_container_type(dict.get_key_type(), buf, r_len, p_full_objects);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ContainerType value_type;
|
||||
value_type.builtin_type = (Variant::Type)dict.get_typed_value_builtin();
|
||||
value_type.class_name = dict.get_typed_value_class_name();
|
||||
value_type.script = dict.get_typed_value_script();
|
||||
|
||||
Error err = _encode_container_type(value_type, buf, r_len, p_full_objects);
|
||||
Error err = _encode_container_type(dict.get_value_type(), buf, r_len, p_full_objects);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
@@ -1894,7 +1861,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
|
||||
if (buf) {
|
||||
buf += len;
|
||||
}
|
||||
Variant *value = dict.getptr(key);
|
||||
const Variant *value = dict.getptr(key);
|
||||
ERR_FAIL_NULL_V(value, ERR_BUG);
|
||||
err = encode_variant(*value, buf, len, p_full_objects, p_depth + 1);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
@@ -1907,15 +1874,10 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
|
||||
|
||||
} break;
|
||||
case Variant::ARRAY: {
|
||||
Array array = p_variant;
|
||||
const Array array = p_variant;
|
||||
|
||||
{
|
||||
ContainerType type;
|
||||
type.builtin_type = (Variant::Type)array.get_typed_builtin();
|
||||
type.class_name = array.get_typed_class_name();
|
||||
type.script = array.get_typed_script();
|
||||
|
||||
Error err = _encode_container_type(type, buf, r_len, p_full_objects);
|
||||
Error err = _encode_container_type(array.get_element_type(), buf, r_len, p_full_objects);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -473,10 +473,11 @@ Error Expression::_get_token(Token &r_token) {
|
||||
} else if (id == "self") {
|
||||
r_token.type = TK_SELF;
|
||||
} else {
|
||||
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
|
||||
if (id == Variant::get_type_name(Variant::Type(i))) {
|
||||
{
|
||||
const Variant::Type type = Variant::get_type_by_name(id);
|
||||
if (type < Variant::VARIANT_MAX) {
|
||||
r_token.type = TK_BASIC_TYPE;
|
||||
r_token.value = i;
|
||||
r_token.value = type;
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,7 @@
|
||||
#include "core/variant/dictionary.h"
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
class ArrayPrivate {
|
||||
public:
|
||||
struct ArrayPrivate {
|
||||
SafeRefCount refcount;
|
||||
Vector<Variant> array;
|
||||
Variant *read_only = nullptr; // If enabled, a pointer is used to a temporary value that is used to return read-only values.
|
||||
@@ -843,6 +842,10 @@ Array::Array(const Array &p_from, uint32_t p_type, const StringName &p_class_nam
|
||||
assign(p_from);
|
||||
}
|
||||
|
||||
void Array::set_typed(const ContainerType &p_element_type) {
|
||||
set_typed(p_element_type.builtin_type, p_element_type.class_name, p_element_type.script);
|
||||
}
|
||||
|
||||
void Array::set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script) {
|
||||
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
|
||||
ERR_FAIL_COND_MSG(_p->array.size() > 0, "Type can only be set when array is empty.");
|
||||
@@ -870,6 +873,14 @@ bool Array::is_same_instance(const Array &p_other) const {
|
||||
return _p == p_other._p;
|
||||
}
|
||||
|
||||
ContainerType Array::get_element_type() const {
|
||||
ContainerType type;
|
||||
type.builtin_type = _p->typed.type;
|
||||
type.class_name = _p->typed.class_name;
|
||||
type.script = _p->typed.script;
|
||||
return type;
|
||||
}
|
||||
|
||||
uint32_t Array::get_typed_builtin() const {
|
||||
return _p->typed.type;
|
||||
}
|
||||
|
||||
@@ -35,11 +35,13 @@
|
||||
|
||||
#include <climits>
|
||||
|
||||
class Variant;
|
||||
class ArrayPrivate;
|
||||
class Callable;
|
||||
class Object;
|
||||
class StringName;
|
||||
class Callable;
|
||||
class Variant;
|
||||
|
||||
struct ArrayPrivate;
|
||||
struct ContainerType;
|
||||
|
||||
class Array {
|
||||
mutable ArrayPrivate *_p;
|
||||
@@ -185,10 +187,14 @@ public:
|
||||
|
||||
const void *id() const;
|
||||
|
||||
void set_typed(const ContainerType &p_element_type);
|
||||
void set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script);
|
||||
|
||||
bool is_typed() const;
|
||||
bool is_same_typed(const Array &p_other) const;
|
||||
bool is_same_instance(const Array &p_other) const;
|
||||
|
||||
ContainerType get_element_type() const;
|
||||
uint32_t get_typed_builtin() const;
|
||||
StringName get_typed_class_name() const;
|
||||
Variant get_typed_script() const;
|
||||
|
||||
@@ -34,6 +34,12 @@
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
struct ContainerType {
|
||||
Variant::Type builtin_type = Variant::NIL;
|
||||
StringName class_name;
|
||||
Ref<Script> script;
|
||||
};
|
||||
|
||||
struct ContainerTypeValidate {
|
||||
Variant::Type type = Variant::NIL;
|
||||
StringName class_name;
|
||||
|
||||
@@ -595,6 +595,10 @@ Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) con
|
||||
return n;
|
||||
}
|
||||
|
||||
void Dictionary::set_typed(const ContainerType &p_key_type, const ContainerType &p_value_type) {
|
||||
set_typed(p_key_type.builtin_type, p_key_type.class_name, p_key_type.script, p_value_type.builtin_type, p_value_type.class_name, p_key_type.script);
|
||||
}
|
||||
|
||||
void Dictionary::set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script) {
|
||||
ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state.");
|
||||
ERR_FAIL_COND_MSG(_p->variant_map.size() > 0, "Type can only be set when dictionary is empty.");
|
||||
@@ -641,6 +645,22 @@ bool Dictionary::is_same_typed_value(const Dictionary &p_other) const {
|
||||
return _p->typed_value == p_other._p->typed_value;
|
||||
}
|
||||
|
||||
ContainerType Dictionary::get_key_type() const {
|
||||
ContainerType type;
|
||||
type.builtin_type = _p->typed_key.type;
|
||||
type.class_name = _p->typed_key.class_name;
|
||||
type.script = _p->typed_key.script;
|
||||
return type;
|
||||
}
|
||||
|
||||
ContainerType Dictionary::get_value_type() const {
|
||||
ContainerType type;
|
||||
type.builtin_type = _p->typed_value.type;
|
||||
type.class_name = _p->typed_value.class_name;
|
||||
type.script = _p->typed_value.script;
|
||||
return type;
|
||||
}
|
||||
|
||||
uint32_t Dictionary::get_typed_key_builtin() const {
|
||||
return _p->typed_key.type;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
class Variant;
|
||||
|
||||
struct ContainerType;
|
||||
struct DictionaryPrivate;
|
||||
|
||||
class Dictionary {
|
||||
@@ -91,13 +92,18 @@ public:
|
||||
Dictionary duplicate(bool p_deep = false) const;
|
||||
Dictionary recursive_duplicate(bool p_deep, int recursion_count) const;
|
||||
|
||||
void set_typed(const ContainerType &p_key_type, const ContainerType &p_value_type);
|
||||
void set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script);
|
||||
|
||||
bool is_typed() const;
|
||||
bool is_typed_key() const;
|
||||
bool is_typed_value() const;
|
||||
bool is_same_typed(const Dictionary &p_other) const;
|
||||
bool is_same_typed_key(const Dictionary &p_other) const;
|
||||
bool is_same_typed_value(const Dictionary &p_other) const;
|
||||
|
||||
ContainerType get_key_type() const;
|
||||
ContainerType get_value_type() const;
|
||||
uint32_t get_typed_key_builtin() const;
|
||||
uint32_t get_typed_value_builtin() const;
|
||||
StringName get_typed_key_class_name() const;
|
||||
|
||||
@@ -176,6 +176,18 @@ String Variant::get_type_name(Variant::Type p_type) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Variant::Type Variant::get_type_by_name(const String &p_type_name) {
|
||||
static HashMap<String, Type> type_names;
|
||||
if (unlikely(type_names.is_empty())) {
|
||||
for (int i = 0; i < VARIANT_MAX; i++) {
|
||||
type_names[get_type_name((Type)i)] = (Type)i;
|
||||
}
|
||||
}
|
||||
|
||||
const Type *ptr = type_names.getptr(p_type_name);
|
||||
return (ptr == nullptr) ? VARIANT_MAX : *ptr;
|
||||
}
|
||||
|
||||
bool Variant::can_convert(Variant::Type p_type_from, Variant::Type p_type_to) {
|
||||
if (p_type_from == p_type_to) {
|
||||
return true;
|
||||
|
||||
@@ -354,6 +354,7 @@ public:
|
||||
return type;
|
||||
}
|
||||
static String get_type_name(Variant::Type p_type);
|
||||
static Variant::Type get_type_by_name(const String &p_type_name);
|
||||
static bool can_convert(Type p_type_from, Type p_type_to);
|
||||
static bool can_convert_strict(Type p_type_from, Type p_type_to);
|
||||
static bool is_type_shared(Variant::Type p_type);
|
||||
|
||||
@@ -40,11 +40,15 @@
|
||||
<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" />
|
||||
<param index="1" name="full_objects" 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.
|
||||
Converts a native engine type to a JSON-compliant value.
|
||||
By default, objects are ignored for security reasons, unless [param full_objects] is [code]true[/code].
|
||||
You can convert a native value to a JSON string like this:
|
||||
[codeblock]
|
||||
func encode_data(value, full_objects = false):
|
||||
return JSON.stringify(JSON.from_native(value, full_objects))
|
||||
[/codeblock]
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_error_line" qualifiers="const">
|
||||
@@ -136,11 +140,15 @@
|
||||
<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" />
|
||||
<param index="1" name="allow_objects" 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.
|
||||
Converts a JSON-compliant value that was created with [method from_native] back to native engine types.
|
||||
By default, objects are ignored for security reasons, unless [param allow_objects] is [code]true[/code].
|
||||
You can convert a JSON string back to a native value like this:
|
||||
[codeblock]
|
||||
func decode_data(string, allow_objects = false):
|
||||
return JSON.to_native(JSON.parse_string(string), allow_objects)
|
||||
[/codeblock]
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
|
||||
@@ -112,15 +112,13 @@ bool EditorAutoloadSettings::_autoload_name_is_valid(const String &p_name, Strin
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
|
||||
if (Variant::get_type_name(Variant::Type(i)) == p_name) {
|
||||
if (Variant::get_type_by_name(p_name) < Variant::VARIANT_MAX) {
|
||||
if (r_error) {
|
||||
*r_error = TTR("Invalid name.") + " " + TTR("Must not collide with an existing built-in type name.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) {
|
||||
if (CoreConstants::get_global_constant_name(i) == p_name) {
|
||||
|
||||
@@ -3858,14 +3858,11 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
|
||||
return OK;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
|
||||
Variant::Type t = Variant::Type(i);
|
||||
if (Variant::get_type_name(t) == p_symbol) {
|
||||
if (Variant::get_type_by_name(p_symbol) < Variant::VARIANT_MAX) {
|
||||
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS;
|
||||
r_result.class_name = Variant::get_type_name(t);
|
||||
r_result.class_name = p_symbol;
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
if ("Variant" == p_symbol) {
|
||||
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS;
|
||||
|
||||
@@ -33,128 +33,195 @@
|
||||
|
||||
#include "core/io/json.h"
|
||||
|
||||
#include "core/variant/typed_array.h"
|
||||
#include "core/variant/typed_dictionary.h"
|
||||
#include "tests/test_macros.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;
|
||||
String encode(const Variant &p_variant, bool p_full_objects = false) {
|
||||
return JSON::stringify(JSON::from_native(p_variant, p_full_objects), "", false);
|
||||
}
|
||||
|
||||
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;
|
||||
Variant decode(const String &p_string, bool p_allow_objects = false) {
|
||||
return JSON::to_native(JSON::parse_string(p_string), p_allow_objects);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
void test(const Variant &p_variant, const String &p_string, bool p_with_objects = false) {
|
||||
CHECK(encode(p_variant, p_with_objects) == p_string);
|
||||
CHECK(decode(p_string, p_with_objects).get_construct_string() == p_variant.get_construct_string());
|
||||
}
|
||||
|
||||
return true;
|
||||
TEST_CASE("[JSON][Native] Conversion between native and JSON formats") {
|
||||
// `Nil` and `bool` (represented as JSON keyword literals).
|
||||
test(Variant(), "null");
|
||||
test(false, "false");
|
||||
test(true, "true");
|
||||
|
||||
// Numbers and strings (represented as JSON strings).
|
||||
test(1, R"("i:1")");
|
||||
test(1.0, R"("f:1.0")");
|
||||
test(String("abc"), R"("s:abc")");
|
||||
test(StringName("abc"), R"("sn:abc")");
|
||||
test(NodePath("abc"), R"("np:abc")");
|
||||
|
||||
// Non-serializable types (always empty after deserialization).
|
||||
test(RID(), R"({"type":"RID"})");
|
||||
test(Callable(), R"({"type":"Callable"})");
|
||||
test(Signal(), R"({"type":"Signal"})");
|
||||
|
||||
// Math types.
|
||||
|
||||
test(Vector2(1, 2), R"({"type":"Vector2","args":[1.0,2.0]})");
|
||||
test(Vector2i(1, 2), R"({"type":"Vector2i","args":[1,2]})");
|
||||
test(Rect2(1, 2, 3, 4), R"({"type":"Rect2","args":[1.0,2.0,3.0,4.0]})");
|
||||
test(Rect2i(1, 2, 3, 4), R"({"type":"Rect2i","args":[1,2,3,4]})");
|
||||
test(Vector3(1, 2, 3), R"({"type":"Vector3","args":[1.0,2.0,3.0]})");
|
||||
test(Vector3i(1, 2, 3), R"({"type":"Vector3i","args":[1,2,3]})");
|
||||
test(Transform2D(1, 2, 3, 4, 5, 6), R"({"type":"Transform2D","args":[1.0,2.0,3.0,4.0,5.0,6.0]})");
|
||||
test(Vector4(1, 2, 3, 4), R"({"type":"Vector4","args":[1.0,2.0,3.0,4.0]})");
|
||||
test(Vector4i(1, 2, 3, 4), R"({"type":"Vector4i","args":[1,2,3,4]})");
|
||||
test(Plane(1, 2, 3, 4), R"({"type":"Plane","args":[1.0,2.0,3.0,4.0]})");
|
||||
test(Quaternion(1, 2, 3, 4), R"({"type":"Quaternion","args":[1.0,2.0,3.0,4.0]})");
|
||||
test(AABB(Vector3(1, 2, 3), Vector3(4, 5, 6)), R"({"type":"AABB","args":[1.0,2.0,3.0,4.0,5.0,6.0]})");
|
||||
|
||||
const Basis b(Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9));
|
||||
test(b, R"({"type":"Basis","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]})");
|
||||
|
||||
const Transform3D tr3d(Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9), Vector3(10, 11, 12));
|
||||
test(tr3d, R"({"type":"Transform3D","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0]})");
|
||||
|
||||
const Projection p(Vector4(1, 2, 3, 4), Vector4(5, 6, 7, 8), Vector4(9, 10, 11, 12), Vector4(13, 14, 15, 16));
|
||||
test(p, R"({"type":"Projection","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0]})");
|
||||
|
||||
test(Color(1, 2, 3, 4), R"({"type":"Color","args":[1.0,2.0,3.0,4.0]})");
|
||||
|
||||
// `Object`.
|
||||
|
||||
Ref<Resource> res;
|
||||
res.instantiate();
|
||||
|
||||
// The properties are stored in an array because the order in which they are assigned may be important during initialization.
|
||||
const String res_repr = R"({"type":"Resource","props":["resource_local_to_scene",false,"resource_name","s:","script",null]})";
|
||||
|
||||
test(res, res_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(res) == "null");
|
||||
CHECK(decode(res_repr).get_type() == Variant::NIL);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// `Dictionary`.
|
||||
|
||||
Dictionary dict;
|
||||
dict[false] = true;
|
||||
dict[0] = 1;
|
||||
dict[0.0] = 1.0;
|
||||
|
||||
// Godot dictionaries preserve insertion order, so an array is used for keys/values.
|
||||
test(dict, R"({"type":"Dictionary","args":[false,true,"i:0","i:1","f:0.0","f:1.0"]})");
|
||||
|
||||
TypedDictionary<int64_t, int64_t> int_int_dict;
|
||||
int_int_dict[1] = 2;
|
||||
int_int_dict[3] = 4;
|
||||
|
||||
test(int_int_dict, R"({"type":"Dictionary","key_type":"int","value_type":"int","args":["i:1","i:2","i:3","i:4"]})");
|
||||
|
||||
TypedDictionary<int64_t, Variant> int_var_dict;
|
||||
int_var_dict[1] = "2";
|
||||
int_var_dict[3] = "4";
|
||||
|
||||
test(int_var_dict, R"({"type":"Dictionary","key_type":"int","args":["i:1","s:2","i:3","s:4"]})");
|
||||
|
||||
TypedDictionary<Variant, int64_t> var_int_dict;
|
||||
var_int_dict["1"] = 2;
|
||||
var_int_dict["3"] = 4;
|
||||
|
||||
test(var_int_dict, R"({"type":"Dictionary","value_type":"int","args":["s:1","i:2","s:3","i:4"]})");
|
||||
|
||||
Dictionary dict2;
|
||||
dict2["x"] = res;
|
||||
|
||||
const String dict2_repr = vformat(R"({"type":"Dictionary","args":["s:x",%s]})", res_repr);
|
||||
|
||||
test(dict2, dict2_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(dict2) == R"({"type":"Dictionary","args":["s:x",null]})");
|
||||
CHECK(decode(dict2_repr).get_construct_string() == "{\n\"x\": null\n}");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
TypedDictionary<String, Resource> res_dict;
|
||||
res_dict["x"] = res;
|
||||
|
||||
const String res_dict_repr = vformat(R"({"type":"Dictionary","key_type":"String","value_type":"Resource","args":["s:x",%s]})", res_repr);
|
||||
|
||||
test(res_dict, res_dict_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(res_dict) == "null");
|
||||
CHECK(decode(res_dict_repr).get_type() == Variant::NIL);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// `Array`.
|
||||
|
||||
Array arr;
|
||||
arr.push_back(true);
|
||||
arr.push_back(1);
|
||||
arr.push_back("abc");
|
||||
|
||||
test(arr, R"([true,"i:1","s:abc"])");
|
||||
|
||||
TypedArray<int64_t> int_arr;
|
||||
int_arr.push_back(1);
|
||||
int_arr.push_back(2);
|
||||
int_arr.push_back(3);
|
||||
|
||||
test(int_arr, R"({"type":"Array","elem_type":"int","args":["i:1","i:2","i:3"]})");
|
||||
|
||||
Array arr2;
|
||||
arr2.push_back(1);
|
||||
arr2.push_back(res);
|
||||
arr2.push_back(9);
|
||||
|
||||
const String arr2_repr = vformat(R"(["i:1",%s,"i:9"])", res_repr);
|
||||
|
||||
test(arr2, arr2_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(arr2) == R"(["i:1",null,"i:9"])");
|
||||
CHECK(decode(arr2_repr).get_construct_string() == "[1, null, 9]");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
TypedArray<Resource> res_arr;
|
||||
res_arr.push_back(res);
|
||||
|
||||
const String res_arr_repr = vformat(R"({"type":"Array","elem_type":"Resource","args":[%s]})", res_repr);
|
||||
|
||||
test(res_arr, res_arr_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(res_arr) == "null");
|
||||
CHECK(decode(res_arr_repr).get_type() == Variant::NIL);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Packed arrays.
|
||||
|
||||
test(PackedByteArray({ 1, 2, 3 }), R"({"type":"PackedByteArray","args":[1,2,3]})");
|
||||
test(PackedInt32Array({ 1, 2, 3 }), R"({"type":"PackedInt32Array","args":[1,2,3]})");
|
||||
test(PackedInt64Array({ 1, 2, 3 }), R"({"type":"PackedInt64Array","args":[1,2,3]})");
|
||||
test(PackedFloat32Array({ 1, 2, 3 }), R"({"type":"PackedFloat32Array","args":[1.0,2.0,3.0]})");
|
||||
test(PackedFloat64Array({ 1, 2, 3 }), R"({"type":"PackedFloat64Array","args":[1.0,2.0,3.0]})");
|
||||
test(PackedStringArray({ "a", "b", "c" }), R"({"type":"PackedStringArray","args":["a","b","c"]})");
|
||||
|
||||
const PackedVector2Array pv2arr({ Vector2(1, 2), Vector2(3, 4), Vector2(5, 6) });
|
||||
test(pv2arr, R"({"type":"PackedVector2Array","args":[1.0,2.0,3.0,4.0,5.0,6.0]})");
|
||||
|
||||
const PackedVector3Array pv3arr({ Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9) });
|
||||
test(pv3arr, R"({"type":"PackedVector3Array","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]})");
|
||||
|
||||
const PackedColorArray pcolarr({ Color(1, 2, 3, 4), Color(5, 6, 7, 8), Color(9, 10, 11, 12) });
|
||||
test(pcolarr, R"({"type":"PackedColorArray","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0]})");
|
||||
|
||||
const PackedVector4Array pv4arr({ Vector4(1, 2, 3, 4), Vector4(5, 6, 7, 8), Vector4(9, 10, 11, 12) });
|
||||
test(pv4arr, R"({"type":"PackedVector4Array","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0]})");
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user