/**************************************************************************/ /* json.cpp */ /**************************************************************************/ /* 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. */ /**************************************************************************/ #include "json.h" #include "core/config/engine.h" #include "core/object/script_language.h" #include "core/variant/container_type_validate.h" const char *JSON::tk_name[TK_MAX] = { "'{'", "'}'", "'['", "']'", "identifier", "string", "number", "':'", "','", "EOF", }; String JSON::_make_indent(const String &p_indent, int p_size) { return p_indent.repeat(p_size); } String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_indent, bool p_sort_keys, HashSet &p_markers, bool p_full_precision) { ERR_FAIL_COND_V_MSG(p_cur_indent > Variant::MAX_RECURSION_DEPTH, "...", "JSON structure is too deep. Bailing."); String colon = ":"; String end_statement = ""; if (!p_indent.is_empty()) { colon += " "; end_statement += "\n"; } switch (p_var.get_type()) { case Variant::NIL: return "null"; case Variant::BOOL: return p_var.operator bool() ? "true" : "false"; case Variant::INT: return itos(p_var); case Variant::FLOAT: { double num = p_var; // Only for exactly 0. If we have approximately 0 let the user decide how much // precision they want. if (num == double(0)) { return String("0.0"); } double magnitude = log10(Math::abs(num)); int total_digits = p_full_precision ? 17 : 14; int precision = MAX(1, total_digits - (int)Math::floor(magnitude)); return String::num(num, precision); } case Variant::PACKED_INT32_ARRAY: case Variant::PACKED_INT64_ARRAY: case Variant::PACKED_FLOAT32_ARRAY: case Variant::PACKED_FLOAT64_ARRAY: case Variant::PACKED_STRING_ARRAY: case Variant::ARRAY: { Array a = p_var; if (a.is_empty()) { return "[]"; } String s = "["; s += end_statement; ERR_FAIL_COND_V_MSG(p_markers.has(a.id()), "\"[...]\"", "Converting circular structure to JSON."); p_markers.insert(a.id()); bool first = true; for (const Variant &var : a) { if (first) { first = false; } else { s += ","; s += end_statement; } s += _make_indent(p_indent, p_cur_indent + 1) + _stringify(var, p_indent, p_cur_indent + 1, p_sort_keys, p_markers); } s += end_statement + _make_indent(p_indent, p_cur_indent) + "]"; p_markers.erase(a.id()); return s; } case Variant::DICTIONARY: { String s = "{"; s += end_statement; Dictionary d = p_var; ERR_FAIL_COND_V_MSG(p_markers.has(d.id()), "\"{...}\"", "Converting circular structure to JSON."); p_markers.insert(d.id()); LocalVector keys = d.get_key_list(); if (p_sort_keys) { keys.sort_custom(); } bool first_key = true; for (const Variant &E : keys) { if (first_key) { first_key = false; } else { s += ","; s += end_statement; } s += _make_indent(p_indent, p_cur_indent + 1) + _stringify(String(E), p_indent, p_cur_indent + 1, p_sort_keys, p_markers); s += colon; s += _stringify(d[E], p_indent, p_cur_indent + 1, p_sort_keys, p_markers); } s += end_statement + _make_indent(p_indent, p_cur_indent) + "}"; p_markers.erase(d.id()); return s; } default: return "\"" + String(p_var).json_escape() + "\""; } } Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_token, int &line, String &r_err_str) { while (p_len > 0) { switch (p_str[index]) { case '\n': { line++; index++; break; } case 0: { r_token.type = TK_EOF; return OK; } break; case '{': { r_token.type = TK_CURLY_BRACKET_OPEN; index++; return OK; } case '}': { r_token.type = TK_CURLY_BRACKET_CLOSE; index++; return OK; } case '[': { r_token.type = TK_BRACKET_OPEN; index++; return OK; } case ']': { r_token.type = TK_BRACKET_CLOSE; index++; return OK; } case ':': { r_token.type = TK_COLON; index++; return OK; } case ',': { r_token.type = TK_COMMA; index++; return OK; } case '"': { index++; String str; while (true) { if (p_str[index] == 0) { r_err_str = "Unterminated string"; return ERR_PARSE_ERROR; } else if (p_str[index] == '"') { index++; break; } else if (p_str[index] == '\\') { //escaped characters... index++; char32_t next = p_str[index]; if (next == 0) { r_err_str = "Unterminated string"; return ERR_PARSE_ERROR; } char32_t res = 0; switch (next) { case 'b': res = 8; break; case 't': res = 9; break; case 'n': res = 10; break; case 'f': res = 12; break; case 'r': res = 13; break; case 'u': { // hex number for (int j = 0; j < 4; j++) { char32_t c = p_str[index + j + 1]; if (c == 0) { r_err_str = "Unterminated string"; return ERR_PARSE_ERROR; } if (!is_hex_digit(c)) { r_err_str = "Malformed hex constant in string"; return ERR_PARSE_ERROR; } char32_t v; if (is_digit(c)) { v = c - '0'; } else if (c >= 'a' && c <= 'f') { v = c - 'a'; v += 10; } else if (c >= 'A' && c <= 'F') { v = c - 'A'; v += 10; } else { ERR_PRINT("Bug parsing hex constant."); v = 0; } res <<= 4; res |= v; } index += 4; //will add at the end anyway if ((res & 0xfffffc00) == 0xd800) { if (p_str[index + 1] != '\\' || p_str[index + 2] != 'u') { r_err_str = "Invalid UTF-16 sequence in string, unpaired lead surrogate"; return ERR_PARSE_ERROR; } index += 2; char32_t trail = 0; for (int j = 0; j < 4; j++) { char32_t c = p_str[index + j + 1]; if (c == 0) { r_err_str = "Unterminated string"; return ERR_PARSE_ERROR; } if (!is_hex_digit(c)) { r_err_str = "Malformed hex constant in string"; return ERR_PARSE_ERROR; } char32_t v; if (is_digit(c)) { v = c - '0'; } else if (c >= 'a' && c <= 'f') { v = c - 'a'; v += 10; } else if (c >= 'A' && c <= 'F') { v = c - 'A'; v += 10; } else { ERR_PRINT("Bug parsing hex constant."); v = 0; } trail <<= 4; trail |= v; } if ((trail & 0xfffffc00) == 0xdc00) { res = (res << 10UL) + trail - ((0xd800 << 10UL) + 0xdc00 - 0x10000); index += 4; //will add at the end anyway } else { r_err_str = "Invalid UTF-16 sequence in string, unpaired lead surrogate"; return ERR_PARSE_ERROR; } } else if ((res & 0xfffffc00) == 0xdc00) { r_err_str = "Invalid UTF-16 sequence in string, unpaired trail surrogate"; return ERR_PARSE_ERROR; } } break; case '"': case '\\': case '/': { res = next; } break; default: { r_err_str = "Invalid escape sequence"; return ERR_PARSE_ERROR; } } str += res; } else { if (p_str[index] == '\n') { line++; } str += p_str[index]; } index++; } r_token.type = TK_STRING; r_token.value = str; return OK; } break; default: { if (p_str[index] <= 32) { index++; break; } if (p_str[index] == '-' || is_digit(p_str[index])) { //a number const char32_t *rptr; double number = String::to_float(&p_str[index], &rptr); index += (rptr - &p_str[index]); r_token.type = TK_NUMBER; r_token.value = number; return OK; } else if (is_ascii_alphabet_char(p_str[index])) { String id; while (is_ascii_alphabet_char(p_str[index])) { id += p_str[index]; index++; } r_token.type = TK_IDENTIFIER; r_token.value = id; return OK; } else { r_err_str = "Unexpected character"; return ERR_PARSE_ERROR; } } } } r_err_str = "Unknown error getting token"; return ERR_PARSE_ERROR; } Error JSON::_parse_value(Variant &value, Token &token, const char32_t *p_str, int &index, int p_len, int &line, int p_depth, String &r_err_str) { if (p_depth > Variant::MAX_RECURSION_DEPTH) { r_err_str = "JSON structure is too deep"; return ERR_OUT_OF_MEMORY; } if (token.type == TK_CURLY_BRACKET_OPEN) { Dictionary d; Error err = _parse_object(d, p_str, index, p_len, line, p_depth + 1, r_err_str); if (err) { return err; } value = d; } else if (token.type == TK_BRACKET_OPEN) { Array a; Error err = _parse_array(a, p_str, index, p_len, line, p_depth + 1, r_err_str); if (err) { return err; } value = a; } else if (token.type == TK_IDENTIFIER) { String id = token.value; if (id == "true") { value = true; } else if (id == "false") { value = false; } else if (id == "null") { value = Variant(); } else { r_err_str = vformat("Expected 'true', 'false', or 'null', got '%s'", id); return ERR_PARSE_ERROR; } } else if (token.type == TK_NUMBER) { value = token.value; } else if (token.type == TK_STRING) { value = token.value; } else { r_err_str = vformat("Expected value, got '%s'", String(tk_name[token.type])); return ERR_PARSE_ERROR; } return OK; } Error JSON::_parse_array(Array &array, const char32_t *p_str, int &index, int p_len, int &line, int p_depth, String &r_err_str) { Token token; bool need_comma = false; while (index < p_len) { Error err = _get_token(p_str, index, p_len, token, line, r_err_str); if (err != OK) { return err; } if (token.type == TK_BRACKET_CLOSE) { return OK; } if (need_comma) { if (token.type != TK_COMMA) { r_err_str = "Expected ','"; return ERR_PARSE_ERROR; } else { need_comma = false; continue; } } Variant v; err = _parse_value(v, token, p_str, index, p_len, line, p_depth, r_err_str); if (err) { return err; } array.push_back(v); need_comma = true; } r_err_str = "Expected ']'"; return ERR_PARSE_ERROR; } Error JSON::_parse_object(Dictionary &object, const char32_t *p_str, int &index, int p_len, int &line, int p_depth, String &r_err_str) { bool at_key = true; String key; Token token; bool need_comma = false; while (index < p_len) { if (at_key) { Error err = _get_token(p_str, index, p_len, token, line, r_err_str); if (err != OK) { return err; } if (token.type == TK_CURLY_BRACKET_CLOSE) { return OK; } if (need_comma) { if (token.type != TK_COMMA) { r_err_str = "Expected '}' or ','"; return ERR_PARSE_ERROR; } else { need_comma = false; continue; } } if (token.type != TK_STRING) { r_err_str = "Expected key"; return ERR_PARSE_ERROR; } key = token.value; err = _get_token(p_str, index, p_len, token, line, r_err_str); if (err != OK) { return err; } if (token.type != TK_COLON) { r_err_str = "Expected ':'"; return ERR_PARSE_ERROR; } at_key = false; } else { Error err = _get_token(p_str, index, p_len, token, line, r_err_str); if (err != OK) { return err; } Variant v; err = _parse_value(v, token, p_str, index, p_len, line, p_depth, r_err_str); if (err) { return err; } object[key] = v; need_comma = true; at_key = true; } } r_err_str = "Expected '}'"; return ERR_PARSE_ERROR; } void JSON::set_data(const Variant &p_data) { data = p_data; text.clear(); } Error JSON::_parse_string(const String &p_json, Variant &r_ret, String &r_err_str, int &r_err_line) { const char32_t *str = p_json.ptr(); int idx = 0; int len = p_json.length(); Token token; r_err_line = 0; String aux_key; Error err = _get_token(str, idx, len, token, r_err_line, r_err_str); if (err) { return err; } err = _parse_value(r_ret, token, str, idx, len, r_err_line, 0, r_err_str); // Check if EOF is reached // or it's a type of the next token. if (err == OK && idx < len) { err = _get_token(str, idx, len, token, r_err_line, r_err_str); if (err || token.type != TK_EOF) { r_err_str = "Expected 'EOF'"; // Reset return value to empty `Variant` r_ret = Variant(); return ERR_PARSE_ERROR; } } return err; } Error JSON::parse(const String &p_json_string, bool p_keep_text) { Error err = _parse_string(p_json_string, data, err_str, err_line); if (err == Error::OK) { err_line = 0; } if (p_keep_text) { text = p_json_string; } return err; } String JSON::get_parsed_text() const { return text; } String JSON::stringify(const Variant &p_var, const String &p_indent, bool p_sort_keys, bool p_full_precision) { Ref json; json.instantiate(); HashSet markers; 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 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() { ClassDB::bind_static_method("JSON", D_METHOD("stringify", "data", "indent", "sort_keys", "full_precision"), &JSON::stringify, DEFVAL(""), DEFVAL(true), DEFVAL(false)); ClassDB::bind_static_method("JSON", D_METHOD("parse_string", "json_string"), &JSON::parse_string); ClassDB::bind_method(D_METHOD("parse", "json_text", "keep_text"), &JSON::parse, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_data"), &JSON::get_data); ClassDB::bind_method(D_METHOD("set_data", "data"), &JSON::set_data); ClassDB::bind_method(D_METHOD("get_parsed_text"), &JSON::get_parsed_text); 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("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 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 switch (p_variant.get_type()) { case Variant::NIL: case Variant::BOOL: { return p_variant; } break; case Variant::INT: { return "i:" + String(p_variant); } break; case Variant::FLOAT: { return "f:" + String(p_variant); } break; case Variant::STRING: { return "s:" + String(p_variant); } break; case Variant::STRING_NAME: { return "sn:" + String(p_variant); } break; case Variant::NODE_PATH: { return "np:" + String(p_variant); } break; case Variant::RID: case Variant::CALLABLE: case Variant::SIGNAL: { Dictionary ret; ret[TYPE] = Variant::get_type_name(p_variant.get_type()); return ret; } break; case Variant::VECTOR2: { const Vector2 v = p_variant; Array args = { v.x, v.y }; RETURN_ARGS; } break; case Variant::VECTOR2I: { const Vector2i v = p_variant; Array args = { v.x, v.y }; RETURN_ARGS; } break; case Variant::RECT2: { const Rect2 r = p_variant; Array args = { r.position.x, r.position.y, r.size.width, r.size.height }; RETURN_ARGS; } break; case Variant::RECT2I: { const Rect2i r = p_variant; Array args = { r.position.x, r.position.y, r.size.width, r.size.height }; RETURN_ARGS; } break; case Variant::VECTOR3: { const Vector3 v = p_variant; Array args = { v.x, v.y, v.z }; RETURN_ARGS; } break; case Variant::VECTOR3I: { const Vector3i v = p_variant; Array args = { v.x, v.y, v.z }; RETURN_ARGS; } break; case Variant::TRANSFORM2D: { const Transform2D t = p_variant; Array args = { t[0].x, t[0].y, t[1].x, t[1].y, t[2].x, t[2].y }; RETURN_ARGS; } break; case Variant::VECTOR4: { const Vector4 v = p_variant; Array args = { v.x, v.y, v.z, v.w }; RETURN_ARGS; } break; case Variant::VECTOR4I: { const Vector4i v = p_variant; Array args = { v.x, v.y, v.z, v.w }; RETURN_ARGS; } break; case Variant::PLANE: { const Plane p = p_variant; Array args = { p.normal.x, p.normal.y, p.normal.z, p.d }; RETURN_ARGS; } break; case Variant::QUATERNION: { const Quaternion q = p_variant; Array args = { q.x, q.y, q.z, q.w }; RETURN_ARGS; } break; case Variant::AABB: { const AABB aabb = p_variant; Array args = { aabb.position.x, aabb.position.y, aabb.position.z, aabb.size.x, aabb.size.y, aabb.size.z }; RETURN_ARGS; } break; case Variant::BASIS: { const Basis b = p_variant; Array args = { b.get_column(0).x, b.get_column(0).y, b.get_column(0).z, b.get_column(1).x, b.get_column(1).y, b.get_column(1).z, b.get_column(2).x, b.get_column(2).y, b.get_column(2).z }; RETURN_ARGS; } break; case Variant::TRANSFORM3D: { const Transform3D t = p_variant; Array args = { t.basis.get_column(0).x, t.basis.get_column(0).y, t.basis.get_column(0).z, t.basis.get_column(1).x, t.basis.get_column(1).y, t.basis.get_column(1).z, t.basis.get_column(2).x, t.basis.get_column(2).y, t.basis.get_column(2).z, t.origin.x, t.origin.y, t.origin.z }; RETURN_ARGS; } break; case Variant::PROJECTION: { const Projection p = p_variant; Array args = { p[0].x, p[0].y, p[0].z, p[0].w, p[1].x, p[1].y, p[1].z, p[1].w, p[2].x, p[2].y, p[2].z, p[2].w, p[3].x, p[3].y, p[3].z, p[3].w }; RETURN_ARGS; } break; case Variant::COLOR: { const Color c = p_variant; Array args = { c.r, c.g, c.b, 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(); } 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