1 | /**************************************************************************/ |
2 | /* json.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "json.h" |
32 | |
33 | #include "core/config/engine.h" |
34 | #include "core/string/print_string.h" |
35 | |
36 | const char *JSON::tk_name[TK_MAX] = { |
37 | "'{'" , |
38 | "'}'" , |
39 | "'['" , |
40 | "']'" , |
41 | "identifier" , |
42 | "string" , |
43 | "number" , |
44 | "':'" , |
45 | "','" , |
46 | "EOF" , |
47 | }; |
48 | |
49 | String JSON::_make_indent(const String &p_indent, int p_size) { |
50 | return p_indent.repeat(p_size); |
51 | } |
52 | |
53 | String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_indent, bool p_sort_keys, HashSet<const void *> &p_markers, bool p_full_precision) { |
54 | ERR_FAIL_COND_V_MSG(p_cur_indent > Variant::MAX_RECURSION_DEPTH, "..." , "JSON structure is too deep. Bailing." ); |
55 | |
56 | String colon = ":" ; |
57 | String end_statement = "" ; |
58 | |
59 | if (!p_indent.is_empty()) { |
60 | colon += " " ; |
61 | end_statement += "\n" ; |
62 | } |
63 | |
64 | switch (p_var.get_type()) { |
65 | case Variant::NIL: |
66 | return "null" ; |
67 | case Variant::BOOL: |
68 | return p_var.operator bool() ? "true" : "false" ; |
69 | case Variant::INT: |
70 | return itos(p_var); |
71 | case Variant::FLOAT: { |
72 | double num = p_var; |
73 | if (p_full_precision) { |
74 | // Store unreliable digits (17) instead of just reliable |
75 | // digits (14) so that the value can be decoded exactly. |
76 | return String::num(num, 17 - (int)floor(log10(num))); |
77 | } else { |
78 | // Store only reliable digits (14) by default. |
79 | return String::num(num, 14 - (int)floor(log10(num))); |
80 | } |
81 | } |
82 | case Variant::PACKED_INT32_ARRAY: |
83 | case Variant::PACKED_INT64_ARRAY: |
84 | case Variant::PACKED_FLOAT32_ARRAY: |
85 | case Variant::PACKED_FLOAT64_ARRAY: |
86 | case Variant::PACKED_STRING_ARRAY: |
87 | case Variant::ARRAY: { |
88 | Array a = p_var; |
89 | if (a.size() == 0) { |
90 | return "[]" ; |
91 | } |
92 | String s = "[" ; |
93 | s += end_statement; |
94 | |
95 | ERR_FAIL_COND_V_MSG(p_markers.has(a.id()), "\"[...]\"" , "Converting circular structure to JSON." ); |
96 | p_markers.insert(a.id()); |
97 | |
98 | for (int i = 0; i < a.size(); i++) { |
99 | if (i > 0) { |
100 | s += "," ; |
101 | s += end_statement; |
102 | } |
103 | s += _make_indent(p_indent, p_cur_indent + 1) + _stringify(a[i], p_indent, p_cur_indent + 1, p_sort_keys, p_markers); |
104 | } |
105 | s += end_statement + _make_indent(p_indent, p_cur_indent) + "]" ; |
106 | p_markers.erase(a.id()); |
107 | return s; |
108 | } |
109 | case Variant::DICTIONARY: { |
110 | String s = "{" ; |
111 | s += end_statement; |
112 | Dictionary d = p_var; |
113 | |
114 | ERR_FAIL_COND_V_MSG(p_markers.has(d.id()), "\"{...}\"" , "Converting circular structure to JSON." ); |
115 | p_markers.insert(d.id()); |
116 | |
117 | List<Variant> keys; |
118 | d.get_key_list(&keys); |
119 | |
120 | if (p_sort_keys) { |
121 | keys.sort(); |
122 | } |
123 | |
124 | bool first_key = true; |
125 | for (const Variant &E : keys) { |
126 | if (first_key) { |
127 | first_key = false; |
128 | } else { |
129 | s += "," ; |
130 | s += end_statement; |
131 | } |
132 | s += _make_indent(p_indent, p_cur_indent + 1) + _stringify(String(E), p_indent, p_cur_indent + 1, p_sort_keys, p_markers); |
133 | s += colon; |
134 | s += _stringify(d[E], p_indent, p_cur_indent + 1, p_sort_keys, p_markers); |
135 | } |
136 | |
137 | s += end_statement + _make_indent(p_indent, p_cur_indent) + "}" ; |
138 | p_markers.erase(d.id()); |
139 | return s; |
140 | } |
141 | default: |
142 | return "\"" + String(p_var).json_escape() + "\"" ; |
143 | } |
144 | } |
145 | |
146 | Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_token, int &line, String &r_err_str) { |
147 | while (p_len > 0) { |
148 | switch (p_str[index]) { |
149 | case '\n': { |
150 | line++; |
151 | index++; |
152 | break; |
153 | } |
154 | case 0: { |
155 | r_token.type = TK_EOF; |
156 | return OK; |
157 | } break; |
158 | case '{': { |
159 | r_token.type = TK_CURLY_BRACKET_OPEN; |
160 | index++; |
161 | return OK; |
162 | } |
163 | case '}': { |
164 | r_token.type = TK_CURLY_BRACKET_CLOSE; |
165 | index++; |
166 | return OK; |
167 | } |
168 | case '[': { |
169 | r_token.type = TK_BRACKET_OPEN; |
170 | index++; |
171 | return OK; |
172 | } |
173 | case ']': { |
174 | r_token.type = TK_BRACKET_CLOSE; |
175 | index++; |
176 | return OK; |
177 | } |
178 | case ':': { |
179 | r_token.type = TK_COLON; |
180 | index++; |
181 | return OK; |
182 | } |
183 | case ',': { |
184 | r_token.type = TK_COMMA; |
185 | index++; |
186 | return OK; |
187 | } |
188 | case '"': { |
189 | index++; |
190 | String str; |
191 | while (true) { |
192 | if (p_str[index] == 0) { |
193 | r_err_str = "Unterminated String" ; |
194 | return ERR_PARSE_ERROR; |
195 | } else if (p_str[index] == '"') { |
196 | index++; |
197 | break; |
198 | } else if (p_str[index] == '\\') { |
199 | //escaped characters... |
200 | index++; |
201 | char32_t next = p_str[index]; |
202 | if (next == 0) { |
203 | r_err_str = "Unterminated String" ; |
204 | return ERR_PARSE_ERROR; |
205 | } |
206 | char32_t res = 0; |
207 | |
208 | switch (next) { |
209 | case 'b': |
210 | res = 8; |
211 | break; |
212 | case 't': |
213 | res = 9; |
214 | break; |
215 | case 'n': |
216 | res = 10; |
217 | break; |
218 | case 'f': |
219 | res = 12; |
220 | break; |
221 | case 'r': |
222 | res = 13; |
223 | break; |
224 | case 'u': { |
225 | // hex number |
226 | for (int j = 0; j < 4; j++) { |
227 | char32_t c = p_str[index + j + 1]; |
228 | if (c == 0) { |
229 | r_err_str = "Unterminated String" ; |
230 | return ERR_PARSE_ERROR; |
231 | } |
232 | if (!is_hex_digit(c)) { |
233 | r_err_str = "Malformed hex constant in string" ; |
234 | return ERR_PARSE_ERROR; |
235 | } |
236 | char32_t v; |
237 | if (is_digit(c)) { |
238 | v = c - '0'; |
239 | } else if (c >= 'a' && c <= 'f') { |
240 | v = c - 'a'; |
241 | v += 10; |
242 | } else if (c >= 'A' && c <= 'F') { |
243 | v = c - 'A'; |
244 | v += 10; |
245 | } else { |
246 | ERR_PRINT("Bug parsing hex constant." ); |
247 | v = 0; |
248 | } |
249 | |
250 | res <<= 4; |
251 | res |= v; |
252 | } |
253 | index += 4; //will add at the end anyway |
254 | |
255 | if ((res & 0xfffffc00) == 0xd800) { |
256 | if (p_str[index + 1] != '\\' || p_str[index + 2] != 'u') { |
257 | r_err_str = "Invalid UTF-16 sequence in string, unpaired lead surrogate" ; |
258 | return ERR_PARSE_ERROR; |
259 | } |
260 | index += 2; |
261 | char32_t trail = 0; |
262 | for (int j = 0; j < 4; j++) { |
263 | char32_t c = p_str[index + j + 1]; |
264 | if (c == 0) { |
265 | r_err_str = "Unterminated String" ; |
266 | return ERR_PARSE_ERROR; |
267 | } |
268 | if (!is_hex_digit(c)) { |
269 | r_err_str = "Malformed hex constant in string" ; |
270 | return ERR_PARSE_ERROR; |
271 | } |
272 | char32_t v; |
273 | if (is_digit(c)) { |
274 | v = c - '0'; |
275 | } else if (c >= 'a' && c <= 'f') { |
276 | v = c - 'a'; |
277 | v += 10; |
278 | } else if (c >= 'A' && c <= 'F') { |
279 | v = c - 'A'; |
280 | v += 10; |
281 | } else { |
282 | ERR_PRINT("Bug parsing hex constant." ); |
283 | v = 0; |
284 | } |
285 | |
286 | trail <<= 4; |
287 | trail |= v; |
288 | } |
289 | if ((trail & 0xfffffc00) == 0xdc00) { |
290 | res = (res << 10UL) + trail - ((0xd800 << 10UL) + 0xdc00 - 0x10000); |
291 | index += 4; //will add at the end anyway |
292 | } else { |
293 | r_err_str = "Invalid UTF-16 sequence in string, unpaired lead surrogate" ; |
294 | return ERR_PARSE_ERROR; |
295 | } |
296 | } else if ((res & 0xfffffc00) == 0xdc00) { |
297 | r_err_str = "Invalid UTF-16 sequence in string, unpaired trail surrogate" ; |
298 | return ERR_PARSE_ERROR; |
299 | } |
300 | |
301 | } break; |
302 | case '"': |
303 | case '\\': |
304 | case '/': { |
305 | res = next; |
306 | } break; |
307 | default: { |
308 | r_err_str = "Invalid escape sequence." ; |
309 | return ERR_PARSE_ERROR; |
310 | } |
311 | } |
312 | |
313 | str += res; |
314 | |
315 | } else { |
316 | if (p_str[index] == '\n') { |
317 | line++; |
318 | } |
319 | str += p_str[index]; |
320 | } |
321 | index++; |
322 | } |
323 | |
324 | r_token.type = TK_STRING; |
325 | r_token.value = str; |
326 | return OK; |
327 | |
328 | } break; |
329 | default: { |
330 | if (p_str[index] <= 32) { |
331 | index++; |
332 | break; |
333 | } |
334 | |
335 | if (p_str[index] == '-' || is_digit(p_str[index])) { |
336 | //a number |
337 | const char32_t *rptr; |
338 | double number = String::to_float(&p_str[index], &rptr); |
339 | index += (rptr - &p_str[index]); |
340 | r_token.type = TK_NUMBER; |
341 | r_token.value = number; |
342 | return OK; |
343 | |
344 | } else if (is_ascii_char(p_str[index])) { |
345 | String id; |
346 | |
347 | while (is_ascii_char(p_str[index])) { |
348 | id += p_str[index]; |
349 | index++; |
350 | } |
351 | |
352 | r_token.type = TK_IDENTIFIER; |
353 | r_token.value = id; |
354 | return OK; |
355 | } else { |
356 | r_err_str = "Unexpected character." ; |
357 | return ERR_PARSE_ERROR; |
358 | } |
359 | } |
360 | } |
361 | } |
362 | |
363 | return ERR_PARSE_ERROR; |
364 | } |
365 | |
366 | 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) { |
367 | if (p_depth > Variant::MAX_RECURSION_DEPTH) { |
368 | r_err_str = "JSON structure is too deep. Bailing." ; |
369 | return ERR_OUT_OF_MEMORY; |
370 | } |
371 | |
372 | if (token.type == TK_CURLY_BRACKET_OPEN) { |
373 | Dictionary d; |
374 | Error err = _parse_object(d, p_str, index, p_len, line, p_depth + 1, r_err_str); |
375 | if (err) { |
376 | return err; |
377 | } |
378 | value = d; |
379 | } else if (token.type == TK_BRACKET_OPEN) { |
380 | Array a; |
381 | Error err = _parse_array(a, p_str, index, p_len, line, p_depth + 1, r_err_str); |
382 | if (err) { |
383 | return err; |
384 | } |
385 | value = a; |
386 | } else if (token.type == TK_IDENTIFIER) { |
387 | String id = token.value; |
388 | if (id == "true" ) { |
389 | value = true; |
390 | } else if (id == "false" ) { |
391 | value = false; |
392 | } else if (id == "null" ) { |
393 | value = Variant(); |
394 | } else { |
395 | r_err_str = "Expected 'true','false' or 'null', got '" + id + "'." ; |
396 | return ERR_PARSE_ERROR; |
397 | } |
398 | } else if (token.type == TK_NUMBER) { |
399 | value = token.value; |
400 | } else if (token.type == TK_STRING) { |
401 | value = token.value; |
402 | } else { |
403 | r_err_str = "Expected value, got " + String(tk_name[token.type]) + "." ; |
404 | return ERR_PARSE_ERROR; |
405 | } |
406 | |
407 | return OK; |
408 | } |
409 | |
410 | 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) { |
411 | Token token; |
412 | bool need_comma = false; |
413 | |
414 | while (index < p_len) { |
415 | Error err = _get_token(p_str, index, p_len, token, line, r_err_str); |
416 | if (err != OK) { |
417 | return err; |
418 | } |
419 | |
420 | if (token.type == TK_BRACKET_CLOSE) { |
421 | return OK; |
422 | } |
423 | |
424 | if (need_comma) { |
425 | if (token.type != TK_COMMA) { |
426 | r_err_str = "Expected ','" ; |
427 | return ERR_PARSE_ERROR; |
428 | } else { |
429 | need_comma = false; |
430 | continue; |
431 | } |
432 | } |
433 | |
434 | Variant v; |
435 | err = _parse_value(v, token, p_str, index, p_len, line, p_depth, r_err_str); |
436 | if (err) { |
437 | return err; |
438 | } |
439 | |
440 | array.push_back(v); |
441 | need_comma = true; |
442 | } |
443 | |
444 | r_err_str = "Expected ']'" ; |
445 | return ERR_PARSE_ERROR; |
446 | } |
447 | |
448 | 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) { |
449 | bool at_key = true; |
450 | String key; |
451 | Token token; |
452 | bool need_comma = false; |
453 | |
454 | while (index < p_len) { |
455 | if (at_key) { |
456 | Error err = _get_token(p_str, index, p_len, token, line, r_err_str); |
457 | if (err != OK) { |
458 | return err; |
459 | } |
460 | |
461 | if (token.type == TK_CURLY_BRACKET_CLOSE) { |
462 | return OK; |
463 | } |
464 | |
465 | if (need_comma) { |
466 | if (token.type != TK_COMMA) { |
467 | r_err_str = "Expected '}' or ','" ; |
468 | return ERR_PARSE_ERROR; |
469 | } else { |
470 | need_comma = false; |
471 | continue; |
472 | } |
473 | } |
474 | |
475 | if (token.type != TK_STRING) { |
476 | r_err_str = "Expected key" ; |
477 | return ERR_PARSE_ERROR; |
478 | } |
479 | |
480 | key = token.value; |
481 | err = _get_token(p_str, index, p_len, token, line, r_err_str); |
482 | if (err != OK) { |
483 | return err; |
484 | } |
485 | if (token.type != TK_COLON) { |
486 | r_err_str = "Expected ':'" ; |
487 | return ERR_PARSE_ERROR; |
488 | } |
489 | at_key = false; |
490 | } else { |
491 | Error err = _get_token(p_str, index, p_len, token, line, r_err_str); |
492 | if (err != OK) { |
493 | return err; |
494 | } |
495 | |
496 | Variant v; |
497 | err = _parse_value(v, token, p_str, index, p_len, line, p_depth, r_err_str); |
498 | if (err) { |
499 | return err; |
500 | } |
501 | object[key] = v; |
502 | need_comma = true; |
503 | at_key = true; |
504 | } |
505 | } |
506 | |
507 | r_err_str = "Expected '}'" ; |
508 | return ERR_PARSE_ERROR; |
509 | } |
510 | |
511 | void JSON::set_data(const Variant &p_data) { |
512 | data = p_data; |
513 | text.clear(); |
514 | } |
515 | |
516 | Error JSON::_parse_string(const String &p_json, Variant &r_ret, String &r_err_str, int &r_err_line) { |
517 | const char32_t *str = p_json.ptr(); |
518 | int idx = 0; |
519 | int len = p_json.length(); |
520 | Token token; |
521 | r_err_line = 0; |
522 | String aux_key; |
523 | |
524 | Error err = _get_token(str, idx, len, token, r_err_line, r_err_str); |
525 | if (err) { |
526 | return err; |
527 | } |
528 | |
529 | err = _parse_value(r_ret, token, str, idx, len, r_err_line, 0, r_err_str); |
530 | |
531 | // Check if EOF is reached |
532 | // or it's a type of the next token. |
533 | if (err == OK && idx < len) { |
534 | err = _get_token(str, idx, len, token, r_err_line, r_err_str); |
535 | |
536 | if (err || token.type != TK_EOF) { |
537 | r_err_str = "Expected 'EOF'" ; |
538 | // Reset return value to empty `Variant` |
539 | r_ret = Variant(); |
540 | return ERR_PARSE_ERROR; |
541 | } |
542 | } |
543 | |
544 | return err; |
545 | } |
546 | |
547 | Error JSON::parse(const String &p_json_string, bool p_keep_text) { |
548 | Error err = _parse_string(p_json_string, data, err_str, err_line); |
549 | if (err == Error::OK) { |
550 | err_line = 0; |
551 | } |
552 | if (p_keep_text) { |
553 | text = p_json_string; |
554 | } |
555 | return err; |
556 | } |
557 | |
558 | String JSON::get_parsed_text() const { |
559 | return text; |
560 | } |
561 | |
562 | String JSON::stringify(const Variant &p_var, const String &p_indent, bool p_sort_keys, bool p_full_precision) { |
563 | Ref<JSON> jason; |
564 | jason.instantiate(); |
565 | HashSet<const void *> markers; |
566 | return jason->_stringify(p_var, p_indent, 0, p_sort_keys, markers, p_full_precision); |
567 | } |
568 | |
569 | Variant JSON::parse_string(const String &p_json_string) { |
570 | Ref<JSON> jason; |
571 | jason.instantiate(); |
572 | Error error = jason->parse(p_json_string); |
573 | 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())); |
574 | return jason->get_data(); |
575 | } |
576 | |
577 | void JSON::_bind_methods() { |
578 | ClassDB::bind_static_method("JSON" , D_METHOD("stringify" , "data" , "indent" , "sort_keys" , "full_precision" ), &JSON::stringify, DEFVAL("" ), DEFVAL(true), DEFVAL(false)); |
579 | ClassDB::bind_static_method("JSON" , D_METHOD("parse_string" , "json_string" ), &JSON::parse_string); |
580 | ClassDB::bind_method(D_METHOD("parse" , "json_text" , "keep_text" ), &JSON::parse, DEFVAL(false)); |
581 | |
582 | ClassDB::bind_method(D_METHOD("get_data" ), &JSON::get_data); |
583 | ClassDB::bind_method(D_METHOD("set_data" , "data" ), &JSON::set_data); |
584 | ClassDB::bind_method(D_METHOD("get_parsed_text" ), &JSON::get_parsed_text); |
585 | ClassDB::bind_method(D_METHOD("get_error_line" ), &JSON::get_error_line); |
586 | ClassDB::bind_method(D_METHOD("get_error_message" ), &JSON::get_error_message); |
587 | |
588 | 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. |
589 | } |
590 | |
591 | //// |
592 | |
593 | //////////// |
594 | |
595 | Ref<Resource> ResourceFormatLoaderJSON::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { |
596 | if (r_error) { |
597 | *r_error = ERR_FILE_CANT_OPEN; |
598 | } |
599 | |
600 | if (!FileAccess::exists(p_path)) { |
601 | *r_error = ERR_FILE_NOT_FOUND; |
602 | return Ref<Resource>(); |
603 | } |
604 | |
605 | Ref<JSON> json; |
606 | json.instantiate(); |
607 | |
608 | Error err = json->parse(FileAccess::get_file_as_string(p_path), Engine::get_singleton()->is_editor_hint()); |
609 | if (err != OK) { |
610 | String err_text = "Error parsing JSON file at '" + p_path + "', on line " + itos(json->get_error_line()) + ": " + json->get_error_message(); |
611 | |
612 | if (Engine::get_singleton()->is_editor_hint()) { |
613 | // If running on editor, still allow opening the JSON so the code editor can edit it. |
614 | WARN_PRINT(err_text); |
615 | } else { |
616 | if (r_error) { |
617 | *r_error = err; |
618 | } |
619 | ERR_PRINT(err_text); |
620 | return Ref<Resource>(); |
621 | } |
622 | } |
623 | |
624 | if (r_error) { |
625 | *r_error = OK; |
626 | } |
627 | |
628 | return json; |
629 | } |
630 | |
631 | void ResourceFormatLoaderJSON::get_recognized_extensions(List<String> *p_extensions) const { |
632 | p_extensions->push_back("json" ); |
633 | } |
634 | |
635 | bool ResourceFormatLoaderJSON::handles_type(const String &p_type) const { |
636 | return (p_type == "JSON" ); |
637 | } |
638 | |
639 | String ResourceFormatLoaderJSON::get_resource_type(const String &p_path) const { |
640 | String el = p_path.get_extension().to_lower(); |
641 | if (el == "json" ) { |
642 | return "JSON" ; |
643 | } |
644 | return "" ; |
645 | } |
646 | |
647 | Error ResourceFormatSaverJSON::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) { |
648 | Ref<JSON> json = p_resource; |
649 | ERR_FAIL_COND_V(json.is_null(), ERR_INVALID_PARAMETER); |
650 | |
651 | String source = json->get_parsed_text().is_empty() ? JSON::stringify(json->get_data(), "\t" , false, true) : json->get_parsed_text(); |
652 | |
653 | Error err; |
654 | Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err); |
655 | |
656 | ERR_FAIL_COND_V_MSG(err, err, "Cannot save json '" + p_path + "'." ); |
657 | |
658 | file->store_string(source); |
659 | if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) { |
660 | return ERR_CANT_CREATE; |
661 | } |
662 | |
663 | return OK; |
664 | } |
665 | |
666 | void ResourceFormatSaverJSON::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const { |
667 | Ref<JSON> json = p_resource; |
668 | if (json.is_valid()) { |
669 | p_extensions->push_back("json" ); |
670 | } |
671 | } |
672 | |
673 | bool ResourceFormatSaverJSON::recognize(const Ref<Resource> &p_resource) const { |
674 | return p_resource->get_class_name() == "JSON" ; //only json, not inherited |
675 | } |
676 | |