VirtualBox

source: vbox/trunk/src/VBox/Runtime/testcase/tstRTJson.cpp@ 108869

Last change on this file since 108869 was 108869, checked in by vboxsync, 4 weeks ago

Runtime/RTJson*: Add support for a subset of the JSON5 specification, bugref:10388

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.5 KB
Line 
1/* $Id: tstRTJson.cpp 108869 2025-04-07 11:36:49Z vboxsync $ */
2/** @file
3 * IPRT Testcase - JSON API.
4 */
5
6/*
7 * Copyright (C) 2016-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#include <iprt/json.h>
42
43#include <iprt/err.h>
44#include <iprt/string.h>
45#include <iprt/test.h>
46
47
48/*********************************************************************************************************************************
49* Global Variables *
50*********************************************************************************************************************************/
51static const char g_szJson[] =
52 "{\n"
53 " \"integer\": 100,\n"
54 " \"number\": 22.22,\n"
55 " \"string\": \"test\",\n"
56 " \"array\": [1, 2, 3, 4, 5, \"6\"],\n"
57 " \"subobject\":\n"
58 " {\n"
59 " \"false\": false,\n"
60 " \"true\": true,\n"
61 " \"null\": null\n"
62 " }\n"
63 "}\n";
64
65/**
66 * Some basic tests to detect malformed JSON.
67 */
68static void tstBasic(RTTEST hTest)
69{
70 RTTestSub(hTest, "Basic valid/malformed tests");
71 static struct
72 {
73 const char *pszJson;
74 int iRcResult;
75 } const aTests[] =
76 {
77 { "", VERR_JSON_MALFORMED },
78 { ",", VERR_JSON_MALFORMED },
79 { ":", VERR_JSON_MALFORMED },
80 { " \n\t{", VERR_JSON_MALFORMED },
81 { "}", VERR_JSON_MALFORMED },
82 { "[", VERR_JSON_MALFORMED },
83 { "]", VERR_JSON_MALFORMED },
84 { "[ \"test\" : ", VERR_JSON_MALFORMED },
85 { "null", VINF_SUCCESS },
86 { "true", VINF_SUCCESS },
87 { "false", VINF_SUCCESS },
88 { "100", VINF_SUCCESS },
89 { "\"test\"", VINF_SUCCESS },
90 { "{ }", VINF_SUCCESS },
91 { "[ ]", VINF_SUCCESS },
92 { "[ 100, 200 ]", VINF_SUCCESS },
93 { "{ \"1\": 1 }", VINF_SUCCESS },
94 { "{ \"1\": 1, \"2\": 2 }", VINF_SUCCESS },
95 { "20", VINF_SUCCESS },
96 { "-20", VINF_SUCCESS },
97 { "{\"positive\":20}", VINF_SUCCESS },
98 { "{\"negative\":-20}", VINF_SUCCESS },
99 { "\"\\u0001\"", VINF_SUCCESS },
100 { "\"\\u000\"", VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE },
101 { "\"\\u00\"", VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE },
102 { "\"\\u0\"", VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE },
103 { "\"\\u\"", VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE },
104 { "\"\\uGhKl\"", VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE },
105 { "\"\\u0000z\"", VERR_JSON_INVALID_CODEPOINT },
106 { "\"\\uffff\"", VERR_JSON_INVALID_CODEPOINT },
107 { "\"\\ufffe\"", VERR_JSON_INVALID_CODEPOINT },
108 { "\"\\ufffd\"", VINF_SUCCESS},
109 { "\"\\ufffd1\"", VINF_SUCCESS},
110 { "\"\\ufffd12\"", VINF_SUCCESS},
111 { "\"\\uD801\\udC37\\ud852\\uDf62\"", VINF_SUCCESS }, /* U+10437 U+24B62 */
112 { "\"\\uD801 \\udC37\"", VERR_JSON_MISSING_SURROGATE_PAIR },
113 { "\"\\uD801udC37\"", VERR_JSON_MISSING_SURROGATE_PAIR },
114 { "\"\\uD801\"", VERR_JSON_MISSING_SURROGATE_PAIR },
115 { "\"\\uD801\\\"", VERR_JSON_MISSING_SURROGATE_PAIR },
116 { "\"\\uD801\\u\"", VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE },
117 { "\"\\uD801\\ud\"", VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE },
118 { "\"\\uD801\\udc\"", VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE },
119 { "\"\\uD801\\udc3\"", VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE },
120 { "\"\\uD801\\uDc37\"", VINF_SUCCESS},
121 { "\"\\uDbff\\uDfff\"", VINF_SUCCESS},
122 { "\"\\t\\n\\b\\f\\r\\\\\\/\"", VINF_SUCCESS},
123 };
124 for (unsigned iTest = 0; iTest < RT_ELEMENTS(aTests); iTest++)
125 {
126 RTERRINFOSTATIC ErrInfo;
127 RTJSONVAL hJsonVal = NIL_RTJSONVAL;
128 int rc = RTJsonParseFromString(&hJsonVal, 0 /*fFlags*/, aTests[iTest].pszJson, RTErrInfoInitStatic(&ErrInfo));
129 if (rc != aTests[iTest].iRcResult)
130 {
131 if (RTErrInfoIsSet(&ErrInfo.Core))
132 RTTestFailed(hTest, "RTJsonParseFromString() for \"%s\" failed, expected %Rrc got %Rrc\n%s",
133 aTests[iTest].pszJson, aTests[iTest].iRcResult, rc, ErrInfo.Core.pszMsg);
134 else
135 RTTestFailed(hTest, "RTJsonParseFromString() for \"%s\" failed, expected %Rrc got %Rrc",
136 aTests[iTest].pszJson, aTests[iTest].iRcResult, rc);
137 }
138 else if (rc == VERR_JSON_MALFORMED && !RTErrInfoIsSet(&ErrInfo.Core))
139 RTTestFailed(hTest, "RTJsonParseFromString() did not return error info for \"%s\" failed", aTests[iTest].pszJson);
140 if (RT_SUCCESS(rc))
141 {
142 if (hJsonVal != NIL_RTJSONVAL)
143 RTJsonValueRelease(hJsonVal);
144 else
145 RTTestFailed(hTest, "RTJsonParseFromString() returned success but no value\n");
146 }
147 else if (hJsonVal != NIL_RTJSONVAL)
148 RTTestFailed(hTest, "RTJsonParseFromString() failed but a JSON value was returned\n");
149 }
150}
151
152/**
153 * Some basic tests to detect malformed JSON5.
154 */
155static void tstBasic5(RTTEST hTest)
156{
157 RTTestSub(hTest, "Basic JSON5 valid/malformed tests");
158 static struct
159 {
160 const char *pszJson;
161 int iRcResult;
162 } const aTests[] =
163 {
164 { "// This is a single line comment\nnull", VINF_SUCCESS },
165 { "/* This is a multi line comment\n **/ null", VINF_SUCCESS },
166 { "\'Single quoted \"string\"\'", VINF_SUCCESS },
167 { "0xc0dec0ffee", VINF_SUCCESS },
168 { "-0xffffffffffffffff", VINF_SUCCESS },
169 { "0xfffffffffffffffff", VERR_JSON_MALFORMED },
170 { "[0, 1, ]", VINF_SUCCESS },
171 { "{ \"name\": 1 , }", VINF_SUCCESS },
172 { "{ name: 1 , }", VINF_SUCCESS },
173 { "{ _name0: 1 , }", VINF_SUCCESS },
174 { "{ $name0x: 1 , }", VINF_SUCCESS },
175
176 };
177 for (unsigned iTest = 0; iTest < RT_ELEMENTS(aTests); iTest++)
178 {
179 RTERRINFOSTATIC ErrInfo;
180 RTJSONVAL hJsonVal = NIL_RTJSONVAL;
181 int rc = RTJsonParseFromString(&hJsonVal, RTJSON_PARSE_F_JSON5, aTests[iTest].pszJson, RTErrInfoInitStatic(&ErrInfo));
182 if (rc != aTests[iTest].iRcResult)
183 {
184 if (RTErrInfoIsSet(&ErrInfo.Core))
185 RTTestFailed(hTest, "RTJsonParseFromString() for \"%s\" failed, expected %Rrc got %Rrc\n%s",
186 aTests[iTest].pszJson, aTests[iTest].iRcResult, rc, ErrInfo.Core.pszMsg);
187 else
188 RTTestFailed(hTest, "RTJsonParseFromString() for \"%s\" failed, expected %Rrc got %Rrc",
189 aTests[iTest].pszJson, aTests[iTest].iRcResult, rc);
190 }
191 else if (rc == VERR_JSON_MALFORMED && !RTErrInfoIsSet(&ErrInfo.Core))
192 RTTestFailed(hTest, "RTJsonParseFromString() did not return error info for \"%s\" failed", aTests[iTest].pszJson);
193 if (RT_SUCCESS(rc))
194 {
195 if (hJsonVal != NIL_RTJSONVAL)
196 RTJsonValueRelease(hJsonVal);
197 else
198 RTTestFailed(hTest, "RTJsonParseFromString() returned success but no value\n");
199 }
200 else if (hJsonVal != NIL_RTJSONVAL)
201 RTTestFailed(hTest, "RTJsonParseFromString() failed but a JSON value was returned\n");
202 }
203}
204
205/**
206 * Checks that methods not indended for the given type return the correct error.
207 */
208static void tstCorrectnessRcForInvalidType(RTTEST hTest, RTJSONVAL hJsonVal, RTJSONVALTYPE enmType)
209{
210 bool fSavedMayPanic = RTAssertSetMayPanic(false);
211 bool fSavedQuiet = RTAssertSetQuiet(true);
212
213 if ( enmType != RTJSONVALTYPE_OBJECT
214 && enmType != RTJSONVALTYPE_ARRAY)
215 {
216 /* The iterator API should return errors. */
217 RTJSONIT hJsonIt = NIL_RTJSONIT;
218 RTTEST_CHECK_RC(hTest, RTJsonIteratorBegin(hJsonVal, &hJsonIt), VERR_JSON_VALUE_INVALID_TYPE);
219 }
220
221 if (enmType != RTJSONVALTYPE_ARRAY)
222 {
223 /* The Array access methods should return errors. */
224 uint32_t cItems = 0;
225 RTJSONVAL hJsonValItem = NIL_RTJSONVAL;
226 RTTEST_CHECK(hTest, RTJsonValueGetArraySize(hJsonVal) == 0);
227 RTTEST_CHECK_RC(hTest, RTJsonValueQueryArraySize(hJsonVal, &cItems), VERR_JSON_VALUE_INVALID_TYPE);
228 RTTEST_CHECK_RC(hTest, RTJsonValueQueryByIndex(hJsonVal, 0, &hJsonValItem), VERR_JSON_VALUE_INVALID_TYPE);
229 }
230
231 if (enmType != RTJSONVALTYPE_OBJECT)
232 {
233 /* The object access methods should return errors. */
234 RTJSONVAL hJsonValMember = NIL_RTJSONVAL;
235 RTTEST_CHECK_RC(hTest, RTJsonValueQueryByName(hJsonVal, "test", &hJsonValMember), VERR_JSON_VALUE_INVALID_TYPE);
236 }
237
238 if (enmType != RTJSONVALTYPE_INTEGER)
239 {
240 int64_t i64Num = 0;
241 RTTEST_CHECK_RC(hTest, RTJsonValueQueryInteger(hJsonVal, &i64Num), VERR_JSON_VALUE_INVALID_TYPE);
242 }
243
244 if (enmType != RTJSONVALTYPE_NUMBER)
245 {
246 double rdNum = 0.0;
247 RTTEST_CHECK_RC(hTest, RTJsonValueQueryNumber(hJsonVal, &rdNum), VERR_JSON_VALUE_INVALID_TYPE);
248 }
249
250 if (enmType != RTJSONVALTYPE_STRING)
251 {
252 const char *psz = NULL;
253 RTTEST_CHECK(hTest, RTJsonValueGetString(hJsonVal) == NULL);
254 RTTEST_CHECK_RC(hTest, RTJsonValueQueryString(hJsonVal, &psz), VERR_JSON_VALUE_INVALID_TYPE);
255 }
256
257 RTAssertSetMayPanic(fSavedMayPanic);
258 RTAssertSetQuiet(fSavedQuiet);
259}
260
261/**
262 * Tests the array accessors.
263 */
264static void tstArray(RTTEST hTest, RTJSONVAL hJsonVal)
265{
266 uint32_t cItems = 0;
267 RTTEST_CHECK(hTest, RTJsonValueGetArraySize(hJsonVal) == 6);
268 RTTEST_CHECK_RC_OK(hTest, RTJsonValueQueryArraySize(hJsonVal, &cItems));
269 RTTEST_CHECK(hTest, cItems == RTJsonValueGetArraySize(hJsonVal));
270
271 for (uint32_t i = 1; i <= 5; i++)
272 {
273 int64_t i64Num = 0;
274 RTJSONVAL hJsonValItem = NIL_RTJSONVAL;
275 RTTEST_CHECK_RC_OK_RETV(hTest, RTJsonValueQueryByIndex(hJsonVal, i - 1, &hJsonValItem));
276 RTTEST_CHECK(hTest, RTJsonValueGetType(hJsonValItem) == RTJSONVALTYPE_INTEGER);
277 RTTEST_CHECK_RC_OK_RETV(hTest, RTJsonValueQueryInteger(hJsonValItem, &i64Num));
278 RTTEST_CHECK(hTest, i64Num == (int64_t)i);
279 RTTEST_CHECK(hTest, RTJsonValueRelease(hJsonValItem) == 1);
280 }
281
282 /* Last should be string. */
283 const char *pszStr = NULL;
284 RTJSONVAL hJsonValItem = NIL_RTJSONVAL;
285 RTTEST_CHECK_RC_OK_RETV(hTest, RTJsonValueQueryByIndex(hJsonVal, 5, &hJsonValItem));
286 RTTEST_CHECK(hTest, RTJsonValueGetType(hJsonValItem) == RTJSONVALTYPE_STRING);
287 RTTEST_CHECK_RC_OK_RETV(hTest, RTJsonValueQueryString(hJsonValItem, &pszStr));
288 RTTEST_CHECK(hTest, RTJsonValueGetString(hJsonValItem) == pszStr);
289 RTTEST_CHECK(hTest, strcmp(pszStr, "6") == 0);
290 RTTEST_CHECK(hTest, RTJsonValueRelease(hJsonValItem) == 1);
291}
292
293/**
294 * Tests the iterator API for the given JSON array or object value.
295 */
296static void tstIterator(RTTEST hTest, RTJSONVAL hJsonVal)
297{
298 RTJSONIT hJsonIt = NIL_RTJSONIT;
299 int rc = RTJsonIteratorBegin(hJsonVal, &hJsonIt);
300 RTTEST_CHECK(hTest, RT_SUCCESS(rc));
301 if (RT_SUCCESS(rc))
302 {
303 const char *pszName = NULL;
304 RTJSONVAL hJsonValMember = NIL_RTJSONVAL;
305 rc = RTJsonIteratorQueryValue(hJsonIt, &hJsonValMember, &pszName);
306 RTTEST_CHECK(hTest, RT_SUCCESS(rc));
307 RTTEST_CHECK(hTest, pszName != NULL);
308 RTTEST_CHECK(hTest, hJsonValMember != NIL_RTJSONVAL);
309 while (RT_SUCCESS(rc))
310 {
311 RTJSONVALTYPE enmTypeMember = RTJsonValueGetType(hJsonValMember);
312 tstCorrectnessRcForInvalidType(hTest, hJsonValMember, enmTypeMember);
313
314 switch (enmTypeMember)
315 {
316 case RTJSONVALTYPE_OBJECT:
317 RTTEST_CHECK(hTest, strcmp(pszName, "subobject") == 0);
318 tstIterator(hTest, hJsonValMember);
319 break;
320 case RTJSONVALTYPE_ARRAY:
321 RTTEST_CHECK(hTest, strcmp(pszName, "array") == 0);
322 tstArray(hTest, hJsonValMember);
323 break;
324 case RTJSONVALTYPE_STRING:
325 {
326 RTTEST_CHECK(hTest, strcmp(pszName, "string") == 0);
327 const char *pszStr = NULL;
328 RTTEST_CHECK_RC_OK(hTest, RTJsonValueQueryString(hJsonValMember, &pszStr));
329 RTTEST_CHECK(hTest, strcmp(pszStr, "test") == 0);
330 break;
331 }
332 case RTJSONVALTYPE_INTEGER:
333 {
334 RTTEST_CHECK(hTest, strcmp(pszName, "integer") == 0);
335 int64_t i64Num = 0;
336 RTTEST_CHECK_RC_OK(hTest, RTJsonValueQueryInteger(hJsonValMember, &i64Num));
337 RTTEST_CHECK(hTest, i64Num == 100);
338 break;
339 }
340 case RTJSONVALTYPE_NUMBER:
341 {
342 RTTEST_CHECK(hTest, strcmp(pszName, "number") == 0);
343 double rdNum = 0.0;
344 RTTEST_CHECK_RC_OK(hTest, RTJsonValueQueryNumber(hJsonValMember, &rdNum));
345 double const rdExpect = 22.22;
346 RTTEST_CHECK(hTest, rdNum == rdExpect);
347 break;
348 }
349 case RTJSONVALTYPE_NULL:
350 RTTEST_CHECK(hTest, strcmp(pszName, "null") == 0);
351 break;
352 case RTJSONVALTYPE_TRUE:
353 RTTEST_CHECK(hTest, strcmp(pszName, "true") == 0);
354 break;
355 case RTJSONVALTYPE_FALSE:
356 RTTEST_CHECK(hTest, strcmp(pszName, "false") == 0);
357 break;
358 default:
359 RTTestFailed(hTest, "Invalid JSON value type %u returned\n", enmTypeMember);
360 }
361
362 RTTEST_CHECK(hTest, RTJsonValueRelease(hJsonValMember) == 1);
363 rc = RTJsonIteratorNext(hJsonIt);
364 RTTEST_CHECK(hTest, rc == VINF_SUCCESS || rc == VERR_JSON_ITERATOR_END);
365 if (RT_SUCCESS(rc))
366 RTTEST_CHECK_RC_OK(hTest, RTJsonIteratorQueryValue(hJsonIt, &hJsonValMember, &pszName));
367 }
368 RTJsonIteratorFree(hJsonIt);
369 }
370}
371
372/**
373 * Test that the parser returns the correct values for a valid JSON.
374 */
375static void tstCorrectness(RTTEST hTest)
376{
377 RTTestSub(hTest, "Correctness");
378
379 RTJSONVAL hJsonVal = NIL_RTJSONVAL;
380 RTTEST_CHECK_RC_OK_RETV(hTest, RTJsonParseFromString(&hJsonVal, 0 /*fFlags*/, g_szJson, NULL));
381
382 if (hJsonVal != NIL_RTJSONVAL)
383 {
384 RTJSONVALTYPE enmType = RTJsonValueGetType(hJsonVal);
385 if (enmType == RTJSONVALTYPE_OBJECT)
386 {
387 /* Excercise the other non object APIs to return VERR_JSON_VALUE_INVALID_TYPE. */
388 tstCorrectnessRcForInvalidType(hTest, hJsonVal, enmType);
389 tstIterator(hTest, hJsonVal);
390 }
391 else
392 RTTestFailed(hTest, "RTJsonParseFromString() returned an invalid JSON value, expected OBJECT got %u\n", enmType);
393 RTTEST_CHECK(hTest, RTJsonValueRelease(hJsonVal) == 0);
394 }
395 else
396 RTTestFailed(hTest, "RTJsonParseFromString() returned success but no value\n");
397}
398
399int main(int argc, char **argv)
400{
401 RTTEST hTest;
402 int rc = RTTestInitExAndCreate(argc, &argv, 0, "tstRTJson", &hTest);
403 if (rc)
404 return rc;
405 RTTestBanner(hTest);
406
407 tstBasic(hTest);
408 tstBasic5(hTest);
409 tstCorrectness(hTest);
410 for (int i = 1; i < argc; i++)
411 {
412 RTTestSubF(hTest, "file %Rbn", argv[i]);
413 RTERRINFOSTATIC ErrInfo;
414 RTJSONVAL hFileValue = NIL_RTJSONVAL;
415 rc = RTJsonParseFromFile(&hFileValue, 0 /*fFlags*/, argv[i], RTErrInfoInitStatic(&ErrInfo));
416 if (RT_SUCCESS(rc))
417 RTJsonValueRelease(hFileValue);
418 else if (RTErrInfoIsSet(&ErrInfo.Core))
419 RTTestFailed(hTest, "%Rrc - %s", rc, ErrInfo.Core.pszMsg);
420 else
421 RTTestFailed(hTest, "%Rrc", rc);
422 }
423
424 /*
425 * Summary.
426 */
427 return RTTestSummaryAndDestroy(hTest);
428}
429
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette