Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/tests/core/io/test_json.cpp
45997 views
1
/**************************************************************************/
2
/* test_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 "tests/test_macros.h"
32
33
TEST_FORCE_LINK(test_json)
34
35
#include "core/io/json.h"
36
#include "core/variant/typed_array.h"
37
38
#include <cfloat> // DBL_MAX
39
40
namespace TestJSON {
41
42
TEST_CASE("[JSON] Stringify single data types") {
43
CHECK(JSON::stringify(Variant()) == "null");
44
CHECK(JSON::stringify(false) == "false");
45
CHECK(JSON::stringify(true) == "true");
46
CHECK(JSON::stringify(0) == "0");
47
CHECK(JSON::stringify(12345) == "12345");
48
CHECK(JSON::stringify(0.75) == "0.75");
49
CHECK(JSON::stringify("test") == "\"test\"");
50
CHECK(JSON::stringify("\\\b\f\n\r\t\v\"") == "\"\\\\\\b\\f\\n\\r\\t\\v\\\"\"");
51
}
52
53
TEST_CASE("[JSON] Stringify arrays") {
54
CHECK(JSON::stringify(Array()) == "[]");
55
56
Array int_array;
57
for (int i = 0; i < 10; i++) {
58
int_array.push_back(i);
59
}
60
CHECK(JSON::stringify(int_array) == "[0,1,2,3,4,5,6,7,8,9]");
61
62
Array str_array;
63
str_array.push_back("Hello");
64
str_array.push_back("World");
65
str_array.push_back("!");
66
CHECK(JSON::stringify(str_array) == "[\"Hello\",\"World\",\"!\"]");
67
68
Array indented_array;
69
Array nested_array;
70
for (int i = 0; i < 5; i++) {
71
indented_array.push_back(i);
72
nested_array.push_back(i);
73
}
74
indented_array.push_back(nested_array);
75
CHECK(JSON::stringify(indented_array, "\t") == "[\n\t0,\n\t1,\n\t2,\n\t3,\n\t4,\n\t[\n\t\t0,\n\t\t1,\n\t\t2,\n\t\t3,\n\t\t4\n\t]\n]");
76
77
Array full_precision_array;
78
full_precision_array.push_back(0.12345678901234568);
79
CHECK(JSON::stringify(full_precision_array, "", true, true) == "[0.12345678901234568]");
80
81
Array non_finite_array;
82
non_finite_array.push_back(Math::INF);
83
non_finite_array.push_back(-Math::INF);
84
non_finite_array.push_back(Math::NaN);
85
ERR_PRINT_OFF
86
CHECK(JSON::stringify(non_finite_array) == "[1e99999,-1e99999,null]");
87
88
Array non_finite_round_trip = JSON::parse_string(JSON::stringify(non_finite_array));
89
CHECK(non_finite_round_trip[0] == Variant(Math::INF));
90
CHECK(non_finite_round_trip[1] == Variant(-Math::INF));
91
CHECK(non_finite_round_trip[2].get_type() == Variant::NIL);
92
93
Array self_array;
94
self_array.push_back(self_array);
95
CHECK(JSON::stringify(self_array) == "[\"[...]\"]");
96
self_array.clear();
97
98
Array max_recursion_array;
99
for (int i = 0; i < Variant::MAX_RECURSION_DEPTH + 1; i++) {
100
Array next;
101
next.push_back(max_recursion_array);
102
max_recursion_array = next;
103
}
104
CHECK(JSON::stringify(max_recursion_array).contains("[...]"));
105
ERR_PRINT_ON
106
}
107
108
TEST_CASE("[JSON] Stringify dictionaries") {
109
CHECK(JSON::stringify(Dictionary()) == "{}");
110
111
Dictionary single_entry;
112
single_entry["key"] = "value";
113
CHECK(JSON::stringify(single_entry) == "{\"key\":\"value\"}");
114
115
Dictionary indented;
116
indented["key1"] = "value1";
117
indented["key2"] = 2;
118
CHECK(JSON::stringify(indented, "\t") == "{\n\t\"key1\": \"value1\",\n\t\"key2\": 2\n}");
119
120
Dictionary outer;
121
Dictionary inner;
122
inner["key"] = "value";
123
outer["inner"] = inner;
124
CHECK(JSON::stringify(outer) == "{\"inner\":{\"key\":\"value\"}}");
125
126
Dictionary full_precision_dictionary;
127
full_precision_dictionary["key"] = 0.12345678901234568;
128
CHECK(JSON::stringify(full_precision_dictionary, "", true, true) == "{\"key\":0.12345678901234568}");
129
130
Dictionary non_finite_dictionary;
131
non_finite_dictionary["-inf"] = -Math::INF;
132
non_finite_dictionary["inf"] = Math::INF;
133
non_finite_dictionary["nan"] = Math::NaN;
134
ERR_PRINT_OFF
135
CHECK(JSON::stringify(non_finite_dictionary) == "{\"-inf\":-1e99999,\"inf\":1e99999,\"nan\":null}");
136
137
Dictionary non_finite_round_trip = JSON::parse_string(JSON::stringify(non_finite_dictionary));
138
CHECK(non_finite_round_trip["-inf"] == Variant(-Math::INF));
139
CHECK(non_finite_round_trip["inf"] == Variant(Math::INF));
140
CHECK(non_finite_round_trip["nan"].get_type() == Variant::NIL);
141
142
Dictionary self_dictionary;
143
self_dictionary["key"] = self_dictionary;
144
CHECK(JSON::stringify(self_dictionary) == "{\"key\":\"{...}\"}");
145
self_dictionary.clear();
146
147
Dictionary max_recursion_dictionary;
148
for (int i = 0; i < Variant::MAX_RECURSION_DEPTH + 1; i++) {
149
Dictionary next;
150
next["key"] = max_recursion_dictionary;
151
max_recursion_dictionary = next;
152
}
153
CHECK(JSON::stringify(max_recursion_dictionary).contains("{...:...}"));
154
ERR_PRINT_ON
155
}
156
157
// NOTE: The current JSON parser accepts many non-conformant strings such as
158
// single-quoted strings, duplicate commas and trailing commas.
159
// This is intentionally not tested as users shouldn't rely on this behavior.
160
161
TEST_CASE("[JSON] Parsing single data types") {
162
// Parsing a single data type as JSON is valid per the JSON specification.
163
164
JSON json;
165
166
json.parse("null");
167
CHECK_MESSAGE(
168
json.get_error_line() == 0,
169
"Parsing `null` as JSON should parse successfully.");
170
CHECK_MESSAGE(
171
json.get_data() == Variant(),
172
"Parsing a double quoted string as JSON should return the expected value.");
173
174
json.parse("true");
175
CHECK_MESSAGE(
176
json.get_error_line() == 0,
177
"Parsing boolean `true` as JSON should parse successfully.");
178
CHECK_MESSAGE(
179
json.get_data(),
180
"Parsing boolean `true` as JSON should return the expected value.");
181
182
json.parse("false");
183
CHECK_MESSAGE(
184
json.get_error_line() == 0,
185
"Parsing boolean `false` as JSON should parse successfully.");
186
CHECK_MESSAGE(
187
!json.get_data(),
188
"Parsing boolean `false` as JSON should return the expected value.");
189
190
json.parse("123456");
191
CHECK_MESSAGE(
192
json.get_error_line() == 0,
193
"Parsing an integer number as JSON should parse successfully.");
194
CHECK_MESSAGE(
195
(int)(json.get_data()) == 123456,
196
"Parsing an integer number as JSON should return the expected value.");
197
198
json.parse("0.123456");
199
CHECK_MESSAGE(
200
json.get_error_line() == 0,
201
"Parsing a floating-point number as JSON should parse successfully.");
202
CHECK_MESSAGE(
203
double(json.get_data()) == doctest::Approx(0.123456),
204
"Parsing a floating-point number as JSON should return the expected value.");
205
206
json.parse("\"hello\"");
207
CHECK_MESSAGE(
208
json.get_error_line() == 0,
209
"Parsing a double quoted string as JSON should parse successfully.");
210
CHECK_MESSAGE(
211
json.get_data() == "hello",
212
"Parsing a double quoted string as JSON should return the expected value.");
213
}
214
215
TEST_CASE("[JSON] Parsing arrays") {
216
JSON json;
217
218
// JSON parsing fails if it's split over several lines (even if leading indentation is removed).
219
json.parse(R"(["Hello", "world.", "This is",["a","json","array.",[]], "Empty arrays ahoy:", [[["Gotcha!"]]]])");
220
221
const Array array = json.get_data();
222
CHECK_MESSAGE(
223
json.get_error_line() == 0,
224
"Parsing a JSON array should parse successfully.");
225
CHECK_MESSAGE(
226
array[0] == "Hello",
227
"The parsed JSON should contain the expected values.");
228
const Array sub_array = array[3];
229
CHECK_MESSAGE(
230
sub_array.size() == 4,
231
"The parsed JSON should contain the expected values.");
232
CHECK_MESSAGE(
233
sub_array[1] == "json",
234
"The parsed JSON should contain the expected values.");
235
CHECK_MESSAGE(
236
sub_array[3].hash() == Array().hash(),
237
"The parsed JSON should contain the expected values.");
238
const Array deep_array = Array(Array(array[5])[0])[0];
239
CHECK_MESSAGE(
240
deep_array[0] == "Gotcha!",
241
"The parsed JSON should contain the expected values.");
242
}
243
244
TEST_CASE("[JSON] Parsing objects (dictionaries)") {
245
JSON json;
246
247
json.parse(R"({"name": "Godot Engine", "is_free": true, "bugs": null, "apples": {"red": 500, "green": 0, "blue": -20}, "empty_object": {}})");
248
249
const Dictionary dictionary = json.get_data();
250
CHECK_MESSAGE(
251
dictionary["name"] == "Godot Engine",
252
"The parsed JSON should contain the expected values.");
253
CHECK_MESSAGE(
254
dictionary["is_free"],
255
"The parsed JSON should contain the expected values.");
256
CHECK_MESSAGE(
257
dictionary["bugs"] == Variant(),
258
"The parsed JSON should contain the expected values.");
259
CHECK_MESSAGE(
260
(int)Dictionary(dictionary["apples"])["blue"] == -20,
261
"The parsed JSON should contain the expected values.");
262
CHECK_MESSAGE(
263
dictionary["empty_object"].hash() == Dictionary().hash(),
264
"The parsed JSON should contain the expected values.");
265
}
266
267
TEST_CASE("[JSON] Parsing escape sequences") {
268
// Only certain escape sequences are valid according to the JSON specification.
269
// Others must result in a parsing error instead.
270
271
JSON json;
272
273
TypedArray<String> valid_escapes = { "\";\"", "\\;\\", "/;/", "b;\b", "f;\f", "n;\n", "r;\r", "t;\t" };
274
275
SUBCASE("Basic valid escape sequences") {
276
for (int i = 0; i < valid_escapes.size(); i++) {
277
String valid_escape = valid_escapes[i];
278
String valid_escape_string = valid_escape.get_slicec(';', 0);
279
String valid_escape_value = valid_escape.get_slicec(';', 1);
280
281
String json_string = "\"\\";
282
json_string += valid_escape_string;
283
json_string += "\"";
284
json.parse(json_string);
285
286
CHECK_MESSAGE(
287
json.get_error_line() == 0,
288
vformat("Parsing valid escape sequence `%s` as JSON should parse successfully.", valid_escape_string));
289
290
String json_value = json.get_data();
291
CHECK_MESSAGE(
292
json_value == valid_escape_value,
293
vformat("Parsing valid escape sequence `%s` as JSON should return the expected value.", valid_escape_string));
294
}
295
}
296
297
SUBCASE("Valid unicode escape sequences") {
298
String json_string = "\"\\u0020\"";
299
json.parse(json_string);
300
301
CHECK_MESSAGE(
302
json.get_error_line() == 0,
303
vformat("Parsing valid unicode escape sequence with value `0020` as JSON should parse successfully."));
304
305
String json_value = json.get_data();
306
CHECK_MESSAGE(
307
json_value == " ",
308
vformat("Parsing valid unicode escape sequence with value `0020` as JSON should return the expected value."));
309
}
310
311
SUBCASE("Invalid escape sequences") {
312
ERR_PRINT_OFF
313
for (char32_t i = 0; i < 128; i++) {
314
bool skip = false;
315
for (int j = 0; j < valid_escapes.size(); j++) {
316
String valid_escape = valid_escapes[j];
317
String valid_escape_string = valid_escape.get_slicec(';', 0);
318
if (valid_escape_string[0] == i) {
319
skip = true;
320
break;
321
}
322
}
323
324
if (skip) {
325
continue;
326
}
327
328
String json_string = "\"\\";
329
json_string += i;
330
json_string += "\"";
331
Error err = json.parse(json_string);
332
333
// TODO: Line number is currently kept on 0, despite an error occurring. This should be fixed in the JSON parser.
334
// CHECK_MESSAGE(
335
// json.get_error_line() != 0,
336
// vformat("Parsing invalid escape sequence with ASCII value `%d` as JSON should fail to parse.", i));
337
CHECK_MESSAGE(
338
err == ERR_PARSE_ERROR,
339
vformat("Parsing invalid escape sequence with ASCII value `%d` as JSON should fail to parse with ERR_PARSE_ERROR.", i));
340
}
341
ERR_PRINT_ON
342
}
343
}
344
345
TEST_CASE("[JSON] Serialization") {
346
JSON json;
347
348
struct FpTestCase {
349
double number;
350
String json;
351
};
352
353
struct IntTestCase {
354
int64_t number;
355
String json;
356
};
357
358
struct UIntTestCase {
359
uint64_t number;
360
String json;
361
};
362
363
static FpTestCase fp_tests_default_precision[] = {
364
{ 0.0, "0.0" },
365
{ 1000.1234567890123456789, "1000.12345678901" },
366
{ -1000.1234567890123456789, "-1000.12345678901" },
367
{ DBL_MAX, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
368
{ DBL_MAX - 1, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
369
{ std::pow(2, 53), "9007199254740992.0" },
370
{ -std::pow(2, 53), "-9007199254740992.0" },
371
{ 0.00000000000000011, "0.00000000000000011" },
372
{ -0.00000000000000011, "-0.00000000000000011" },
373
{ 1.0 / 3.0, "0.333333333333333" },
374
{ 0.9999999999999999, "1.0" },
375
{ 1.0000000000000001, "1.0" },
376
};
377
378
static FpTestCase fp_tests_full_precision[] = {
379
{ 0.0, "0.0" },
380
{ 1000.1234567890123456789, "1000.1234567890124" },
381
{ -1000.1234567890123456789, "-1000.1234567890124" },
382
{ DBL_MAX, "1.7976931348623157e+308" },
383
{ DBL_MAX - 1, "1.7976931348623157e+308" },
384
{ std::pow(2, 53), "9.007199254740992e+15" },
385
{ -std::pow(2, 53), "-9.007199254740992e+15" },
386
{ 0.00000000000000011, "1.1e-16" },
387
{ -0.00000000000000011, "-1.1e-16" },
388
{ 1.0 / 3.0, "0.3333333333333333" },
389
{ 0.9999999999999999, "0.9999999999999999" },
390
{ 1.0000000000000001, "1.0" },
391
};
392
393
static IntTestCase int_tests[] = {
394
{ 0, "0" },
395
{ INT64_MAX, "9223372036854775807" },
396
{ INT64_MIN, "-9223372036854775808" },
397
};
398
399
SUBCASE("Floating point default precision") {
400
for (FpTestCase &test : fp_tests_default_precision) {
401
String json_value = json.stringify(test.number, "", true, false);
402
403
CHECK_MESSAGE(
404
json_value == test.json,
405
vformat("Serializing `%.20d` to JSON should return the expected value.", test.number));
406
}
407
}
408
409
SUBCASE("Floating point full precision") {
410
for (FpTestCase &test : fp_tests_full_precision) {
411
String json_value = json.stringify(test.number, "", true, true);
412
413
CHECK_MESSAGE(
414
json_value == test.json,
415
vformat("Serializing `%20f` to JSON should return the expected value.", test.number));
416
}
417
}
418
419
SUBCASE("Signed integer") {
420
for (IntTestCase &test : int_tests) {
421
String json_value = json.stringify(test.number, "", true, true);
422
423
CHECK_MESSAGE(
424
json_value == test.json,
425
vformat("Serializing `%d` to JSON should return the expected value.", test.number));
426
}
427
}
428
}
429
430
} // namespace TestJSON
431
432