#include "util_allocator.h"
#include "cx/test.h"
#include "cx/json.h"
#include "cx/compare.h"
CX_TEST(test_json_init_default) {
CxJson json;
CX_TEST_DO {
cxJsonInit(&json,
NULL);
CX_TEST_ASSERT(json.states == json.states_internal);
CX_TEST_ASSERT(json.states_size ==
1);
CX_TEST_ASSERT(json.states_capacity >=
8);
CX_TEST_ASSERT(json.vbuf == json.vbuf_internal);
CX_TEST_ASSERT(json.vbuf_size ==
0);
CX_TEST_ASSERT(json.vbuf_capacity >=
8);
cxJsonDestroy(&json);
}
}
CX_TEST(test_json_simple_object) {
cxstring text = cx_str(
"{\n"
"\t\"message\":\"success\",\n"
"\t\"position\":{\n"
"\t\t\"longitude\":-94.7099,\n"
"\t\t\"latitude\":51.5539\n"
"\t},\n"
"\t\"timestamp\":1729348561,\n"
"\t\"alive\":true\n"
"}"
);
CX_TEST_DO {
CxJsonStatus result;
CxJson json;
cxJsonInit(&json,
NULL);
cxJsonFill(&json, text);
CxJsonValue *obj;
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsObject(obj));
CxJsonValue *message = cxJsonObjGet(obj,
"message");
CX_TEST_ASSERT(cxJsonIsString(message));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(message),
"success")
);
CxJsonValue *position = cxJsonObjGet(obj,
"position");
CX_TEST_ASSERT(cxJsonIsObject(position));
CxJsonValue *longitude = cxJsonObjGet(position,
"longitude");
CX_TEST_ASSERT(cxJsonIsNumber(longitude));
CX_TEST_ASSERT(!cxJsonIsInteger(longitude));
CX_TEST_ASSERT(
0 == cx_vcmp_double(cxJsonAsDouble(longitude),
-94.7099));
CX_TEST_ASSERT(cxJsonAsInteger(longitude) ==
-94);
CxJsonValue *latitude = cxJsonObjGet(position,
"latitude");
CX_TEST_ASSERT(cxJsonIsNumber(latitude));
CX_TEST_ASSERT(!cxJsonIsInteger(latitude));
CX_TEST_ASSERT(
0 == cx_vcmp_double(cxJsonAsDouble(latitude),
51.5539));
CX_TEST_ASSERT(cxJsonAsInteger(latitude) ==
51);
CxJsonValue *timestamp = cxJsonObjGet(obj,
"timestamp");
CX_TEST_ASSERT(cxJsonIsInteger(timestamp));
CX_TEST_ASSERT(cxJsonIsNumber(timestamp));
CX_TEST_ASSERT(cxJsonAsInteger(timestamp) ==
1729348561);
CX_TEST_ASSERT(cxJsonAsDouble(timestamp) ==
1729348561.0);
CxJsonValue *alive = cxJsonObjGet(obj,
"alive");
CX_TEST_ASSERT(cxJsonIsBool(alive));
CX_TEST_ASSERT(cxJsonIsTrue(alive));
CX_TEST_ASSERT(!cxJsonIsFalse(alive));
CX_TEST_ASSERT(cxJsonAsBool(alive));
cxJsonValueFree(obj);
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_DATA);
cxJsonDestroy(&json);
}
}
CX_TEST(test_json_large_object) {
CxJsonValue *obj = cxJsonCreateObj(
NULL);
CX_TEST_DO {
cxJsonObjPutString(obj,
"mystring",
"test");
char buf[
10];
for (
unsigned i =
0 ; i <
300 ; i++) {
sprintf(buf,
"key %d", i);
cxJsonObjPutInteger(obj, buf, i);
}
CX_TEST_ASSERT(
301 == cxJsonObjSize(obj));
CxJsonValue *v;
v = cxJsonObjGet(obj,
"key 64");
CX_TEST_ASSERT(cxJsonIsInteger(v));
CX_TEST_ASSERT(cxJsonAsInteger(v) ==
64);
v = cxJsonObjGet(obj,
"key 228");
CX_TEST_ASSERT(cxJsonIsInteger(v));
CX_TEST_ASSERT(cxJsonAsInteger(v) ==
228);
v = cxJsonObjGet(obj,
"mystring");
CX_TEST_ASSERT(cxJsonIsString(v));
CX_TEST_ASSERT(
0 == cx_strcmp(cxJsonAsCxMutStr(v),
"test"));
}
cxJsonValueFree(obj);
}
CX_TEST(test_json_from_string) {
cxstring text = cx_str(
"{\n"
"\t\"message\":\"success\",\n"
"\t\"position\":{\n"
"\t\t\"longitude\":-94.7099,\n"
"\t\t\"latitude\":51.5539\n"
"\t},\n"
"\t\"timestamp\":1729348561,\n"
"\t\"alive\":true\n"
"}"
);
CX_TEST_DO {
CxJsonValue *obj;
CX_TEST_ASSERT(cxJsonFromString(
NULL, text, &obj) ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsObject(obj));
CxJsonValue *message = cxJsonObjGet(obj,
"message");
CX_TEST_ASSERT(cxJsonIsString(message));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(message),
"success")
);
CxJsonValue *position = cxJsonObjGet(obj,
"position");
CX_TEST_ASSERT(cxJsonIsObject(position));
CxJsonValue *longitude = cxJsonObjGet(position,
"longitude");
CX_TEST_ASSERT(cxJsonIsNumber(longitude));
CX_TEST_ASSERT(!cxJsonIsInteger(longitude));
CX_TEST_ASSERT(
0 == cx_vcmp_double(cxJsonAsDouble(longitude),
-94.7099));
CX_TEST_ASSERT(cxJsonAsInteger(longitude) ==
-94);
CxJsonValue *latitude = cxJsonObjGet(position,
"latitude");
CX_TEST_ASSERT(cxJsonIsNumber(latitude));
CX_TEST_ASSERT(!cxJsonIsInteger(latitude));
CX_TEST_ASSERT(
0 == cx_vcmp_double(cxJsonAsDouble(latitude),
51.5539));
CX_TEST_ASSERT(cxJsonAsInteger(latitude) ==
51);
CxJsonValue *timestamp = cxJsonObjGet(obj,
"timestamp");
CX_TEST_ASSERT(cxJsonIsInteger(timestamp));
CX_TEST_ASSERT(cxJsonIsNumber(timestamp));
CX_TEST_ASSERT(cxJsonAsInteger(timestamp) ==
1729348561);
CX_TEST_ASSERT(cxJsonAsDouble(timestamp) ==
1729348561.0);
CxJsonValue *alive = cxJsonObjGet(obj,
"alive");
CX_TEST_ASSERT(cxJsonIsBool(alive));
CX_TEST_ASSERT(cxJsonIsTrue(alive));
CX_TEST_ASSERT(!cxJsonIsFalse(alive));
CX_TEST_ASSERT(cxJsonAsBool(alive));
cxJsonValueFree(obj);
}
}
CX_TEST(test_json_from_string_errors) {
CX_TEST_DO {
CxJsonValue *obj =
NULL;
CX_TEST_ASSERT(cxJsonFromString(
NULL,
"", &obj) ==
CX_JSON_NO_DATA);
CX_TEST_ASSERT(cxJsonFromString(
NULL, cx_str(
NULL), &obj) ==
CX_JSON_NO_DATA);
CX_TEST_ASSERT(cxJsonFromString(
NULL,
"\"incomplete", &obj) ==
CX_JSON_INCOMPLETE_DATA);
CX_TEST_ASSERT(cxJsonFromString(
NULL,
"{ \"incomplete\": ", &obj) ==
CX_JSON_INCOMPLETE_DATA);
CX_TEST_ASSERT(cxJsonFromString(
NULL,
"{\"number\": 47110815!}", &obj) ==
CX_JSON_FORMAT_ERROR_NUMBER);
CX_TEST_ASSERT(cxJsonFromString(
NULL,
"[ \"unexpected token\" : true ]", &obj) ==
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
CX_TEST_ASSERT(cxJsonFromString(
NULL,
"} [", &obj) ==
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
CX_TEST_ASSERT(obj && obj->type ==
CX_JSON_NOTHING);
}
}
CX_TEST(test_json_from_string_multiple_values) {
CxJsonStatus status;
CxJsonValue *obj;
CX_TEST_DO {
obj =
NULL;
status = cxJsonFromString(
NULL,
"{ \"obj1\": \"hello\" }\n\"value2\"\n", &obj);
CX_TEST_ASSERT(obj !=
NULL);
CX_TEST_ASSERT(obj->type ==
CX_JSON_NOTHING);
CX_TEST_ASSERT(status ==
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
obj =
NULL;
status = cxJsonFromString(
NULL,
"\"value\" \n ] syntax error [", &obj);
CX_TEST_ASSERT(obj !=
NULL);
CX_TEST_ASSERT(obj->type ==
CX_JSON_NOTHING);
CX_TEST_ASSERT(status ==
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
}
}
CX_TEST(test_json_from_string_untrimmed) {
CxJsonStatus status;
CxJsonValue *obj;
CX_TEST_DO {
obj =
NULL;
status = cxJsonFromString(
NULL,
"\n\t{ \"obj1\": \"hello\" } \n", &obj);
CX_TEST_ASSERT(status ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsObject(obj));
CxJsonValue *obj1 = cxJsonObjGet(obj,
"obj1");
CX_TEST_ASSERT(cxJsonIsString(obj1));
CX_TEST_ASSERT(cx_strcmp(cxJsonAsCxString(obj1),
"hello") ==
0);
cxJsonValueFree(obj);
}
}
CX_TEST(test_json_escaped_strings) {
cxstring text = cx_str(
"{\n"
"\t\"object\":\"{\\n\\t\\\"object\\\":null\\n}\",\n"
"\t\"ctrl-chars\":\"\\\\foo\\r\\nbar\\f*ring\\/ring*\\b\"\n"
"}"
);
CxJson json;
cxJsonInit(&json,
NULL);
CX_TEST_DO {
cxJsonFill(&json, text);
CxJsonValue *obj;
CxJsonStatus result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsObject(obj));
CxJsonValue *object = cxJsonObjGet(obj,
"object");
CX_TEST_ASSERT(cxJsonIsString(object));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(object),
"{\n\t\"object\":null\n}")
);
CxJsonValue *ctrl = cxJsonObjGet(obj,
"ctrl-chars");
CX_TEST_ASSERT(cxJsonIsString(ctrl));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(ctrl),
"\\foo\r\nbar\f*ring/ring*\b")
);
cxJsonValueFree(obj);
}
cxJsonDestroy(&json);
}
CX_TEST(test_json_escaped_unicode_strings) {
cxstring text = cx_str(
"{\n"
"\"ascii\":\"\\u0041\\u0053\\u0043\\u0049\\u0049\",\n"
"\"unicode\":\"\\u00df\\u00DF\",\n"
"\"mixed\":\"mixed ä ö \\u00e4 \\u00f6\",\n"
"\"wide\":\"\\u03a3\\u29b0\",\n"
"\"surrogatepair1\":\"\\ud83e\\udff5\",\n"
"\"surrogatepair2\":\"test\\ud83e\\udff1AA\"\n,"
"\"mixed2\":\"123\\u03a3\\ud83e\\udfc5\\u00df\""
"}"
);
CxJson json;
cxJsonInit(&json,
NULL);
CX_TEST_DO {
cxJsonFill(&json, text);
CxJsonValue *obj;
CxJsonStatus result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsObject(obj));
CxJsonValue *ascii = cxJsonObjGet(obj,
"ascii");
CX_TEST_ASSERT(cxJsonIsString(ascii));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(ascii),
"ASCII")
);
CxJsonValue *unicode = cxJsonObjGet(obj,
"unicode");
CX_TEST_ASSERT(cxJsonIsString(unicode));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(unicode),
"ßß")
);
CxJsonValue *mixed = cxJsonObjGet(obj,
"mixed");
CX_TEST_ASSERT(cxJsonIsString(mixed));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(mixed),
"mixed ä ö ä ö")
);
CxJsonValue *wide = cxJsonObjGet(obj,
"wide");
CX_TEST_ASSERT(cxJsonIsString(wide));
CX_TEST_ASSERT(
0 == cx_strcmp(cxJsonAsCxString(wide),
"Σ⦰"));
CxJsonValue *surrogatepair1 = cxJsonObjGet(obj,
"surrogatepair1");
CX_TEST_ASSERT(cxJsonIsString(surrogatepair1));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(surrogatepair1),
"\xf0\x9f\xaf\xb5")
);
CxJsonValue *surrogatepair2 = cxJsonObjGet(obj,
"surrogatepair2");
CX_TEST_ASSERT(cxJsonIsString(surrogatepair2));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(surrogatepair2),
"test\xf0\x9f\xaf\xb1" "AA")
);
CxJsonValue *mixed2 = cxJsonObjGet(obj,
"mixed2");
char test[
16];
strncpy(test, mixed2->string.ptr,
15);
CX_TEST_ASSERT(cxJsonIsString(mixed2));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(mixed2),
"123\xce\xa3\xf0\x9f\xaf\x85ß")
);
cxJsonValueFree(obj);
}
cxJsonDestroy(&json);
}
CX_TEST(test_json_escaped_unicode_malformed) {
CxJson json;
cxJsonInit(&json,
NULL);
CxJsonValue *obj;
CxJsonStatus result;
CX_TEST_DO {
cxJsonFill(&json,
"\"too few digits \\u123\"");
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsString(obj));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(obj),
"too few digits \\u123"
));
cxJsonValueFree(obj);
cxJsonFill(&json,
"\"too many digits \\u00E456\"");
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsString(obj));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(obj),
"too many digits ä56"
));
cxJsonValueFree(obj);
cxJsonFill(&json,
"\"only high \\uD800 surrogate\"");
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsString(obj));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(obj),
"only high \\uD800 surrogate"
));
cxJsonValueFree(obj);
cxJsonFill(&json,
"\"only low \\uDC00 surrogate\"");
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsString(obj));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(obj),
"only low \\uDC00 surrogate"
));
cxJsonValueFree(obj);
cxJsonFill(&json,
"\"two high \\uD800\\uD800 surrogates\"");
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsString(obj));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(obj),
"two high \\uD800\\uD800 surrogates"
));
cxJsonValueFree(obj);
cxJsonFill(&json,
"\"high plus bullshit \\uD800\\u567 foo\"");
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsString(obj));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(obj),
"high plus bullshit \\uD800\\u567 foo"
));
cxJsonValueFree(obj);
}
cxJsonDestroy(&json);
}
CX_TEST(test_json_escaped_end_of_string) {
CxJson json;
cxJsonInit(&json,
NULL);
CX_TEST_DO {
cxJsonFill(&json,
"\"a \\\"test\\\" string\"");
CxJsonValue *val;
CxJsonStatus result = cxJsonNext(&json, &val);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsString(val));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(val),
"a \"test\" string")
);
cxJsonValueFree(val);
cxJsonFill(&json,
"\"a \\\"test\\");
result = cxJsonNext(&json, &val);
CX_TEST_ASSERT(result ==
CX_JSON_INCOMPLETE_DATA);
cxJsonFill(&json,
"\" string\"");
result = cxJsonNext(&json, &val);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsString(val));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(val),
"a \"test\" string")
);
cxJsonValueFree(val);
}
cxJsonDestroy(&json);
}
CX_TEST(test_json_object_incomplete_token) {
cxstring text = cx_str(
"{\"message\":\"success\" , \"__timestamp\":1729348561}");
cxstring parts[
16];
size_t nparts =
0;
for(
size_t i=
0;i<text.length;i+=
4) {
parts[nparts++] = cx_strsubsl(text, i,
4);
}
CX_TEST_DO {
CxJsonStatus result;
CxJson json;
cxJsonInit(&json,
NULL);
CxJsonValue *obj;
size_t part =
0;
while(part < nparts -
1) {
cxJsonFill(&json, parts[part]);
part++;
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_INCOMPLETE_DATA);
}
cxJsonFill(&json, parts[nparts -
1]);
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsObject(obj));
CxJsonValue *message = cxJsonObjGet(obj,
"message");
CX_TEST_ASSERT(cxJsonIsString(message));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(message),
"success")
);
CxJsonValue *timestamp = cxJsonObjGet(obj,
"__timestamp");
CX_TEST_ASSERT(message->type ==
CX_JSON_STRING);
CX_TEST_ASSERT(cxJsonIsInteger(timestamp));
CX_TEST_ASSERT(cxJsonAsInteger(timestamp) ==
1729348561);
cxJsonValueFree(obj);
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_DATA);
cxJsonReset(&json);
cxJsonFill(&json,
"\"incomplete token");
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_INCOMPLETE_DATA);
cxJsonDestroy(&json);
}
}
CX_TEST(test_json_token_wrongly_completed) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
const CxAllocator *alloc = &talloc.base;
cxstring text = cx_str(
"{\"number\": 47110815!}");
cxstring part1 = cx_strsubsl(text,
0,
16);
cxstring part2 = cx_strsubs(text,
16);
CX_TEST_DO {
CxJson json;
cxJsonInit(&json, alloc);
CxJsonStatus result;
CxJsonValue *obj;
cxJsonFill(&json, part1);
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_INCOMPLETE_DATA);
cxJsonFill(&json, part2);
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_FORMAT_ERROR_NUMBER);
CX_TEST_ASSERT(obj->type ==
CX_JSON_NOTHING);
cxJsonDestroy(&json);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_json_parenthesis_mismatch) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
const CxAllocator *alloc = &talloc.base;
CX_TEST_DO {
CxJson json;
cxJsonInit(&json, alloc);
CxJsonStatus result;
CxJsonValue *obj;
cxJsonFill(&json,
"[0, 1, 2, 3}");
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
cxJsonReset(&json);
cxJsonFill(&json,
"{\"test\": 42]");
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
cxJsonDestroy(&json);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_json_object_name_is_no_string) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
const CxAllocator *alloc = &talloc.base;
CX_TEST_DO {
CxJson json;
cxJsonInit(&json, alloc);
CxJsonStatus result;
CxJsonValue *obj;
cxJsonFill(&json,
"{42: \"test\"}");
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
cxJsonDestroy(&json);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_json_subsequent_fill) {
cxstring text = cx_str(
"{\"message\":\"success\" , \"__timestamp\":1729348561}");
cxstring part1 = cx_strsubsl(text,
0,
25);
cxstring part2 = cx_strsubs(text,
25);
CX_TEST_DO {
CxJson json;
cxJsonInit(&json,
NULL);
CxJsonValue *obj;
cxJsonFill(&json, part1);
cxJsonFill(&json, part2);
CxJsonStatus result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsObject(obj));
CxJsonValue *message = cxJsonObjGet(obj,
"message");
CX_TEST_ASSERT(cxJsonIsString(message));
CX_TEST_ASSERT(
0 == cx_strcmp(
cxJsonAsCxString(message),
"success")
);
CxJsonValue *timestamp = cxJsonObjGet(obj,
"__timestamp");
CX_TEST_ASSERT(message->type ==
CX_JSON_STRING);
CX_TEST_ASSERT(cxJsonIsInteger(timestamp));
CX_TEST_ASSERT(cxJsonAsInteger(timestamp) ==
1729348561);
cxJsonValueFree(obj);
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_DATA);
cxJsonDestroy(&json);
}
}
CX_TEST(test_json_no_fill) {
CxJson json;
cxJsonInit(&json,
NULL);
CX_TEST_DO {
CxJsonValue *obj =
NULL;
CxJsonStatus result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NULL_DATA);
CX_TEST_ASSERT(obj !=
NULL);
CX_TEST_ASSERT(obj->type ==
CX_JSON_NOTHING);
}
cxJsonDestroy(&json);
}
CX_TEST(test_json_null_fill) {
CxJson json;
cxJsonInit(&json,
NULL);
CX_TEST_DO {
CxJsonStatus result;
CxJsonValue *obj =
NULL;
cxstring nullstr = cx_strn(
NULL,
0);
cxJsonFill(&json, nullstr);
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_DATA);
CX_TEST_ASSERT(obj !=
NULL);
CX_TEST_ASSERT(obj->type ==
CX_JSON_NOTHING);
obj =
NULL;
cxJsonFill(&json,
"[0, 1");
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_INCOMPLETE_DATA);
CX_TEST_ASSERT(obj !=
NULL);
CX_TEST_ASSERT(obj->type ==
CX_JSON_NOTHING);
obj =
NULL;
cxJsonFill(&json, nullstr);
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_INCOMPLETE_DATA);
CX_TEST_ASSERT(obj !=
NULL);
CX_TEST_ASSERT(obj->type ==
CX_JSON_NOTHING);
obj =
NULL;
cxJsonFill(&json,
", 2]");
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(obj !=
NULL);
CX_TEST_ASSERT(cxJsonIsArray(obj));
cxJsonValueFree(obj);
cxJsonFill(&json, nullstr);
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_DATA);
CX_TEST_ASSERT(obj !=
NULL);
CX_TEST_ASSERT(obj->type ==
CX_JSON_NOTHING);
}
cxJsonDestroy(&json);
}
CX_TEST(test_json_object_error) {
cxstring text0 = cx_str(
"{\n"
"\t\"message\":\"success\",\n"
"\t\"data\":{\n"
"\t\t\"obj\":{\n"
"\t\t\t\"array\": [1, 2, 3, ?syntaxerror? ]\n"
"\t\t}\n"
"\t},\n"
"\t\"timestamp\":1729348561,\n"
"}"
);
cxstring text1 = cx_str(
"{ \"string\" }");
cxstring text2 = cx_str(
"{ \"a\" : }");
cxstring text3 = cx_str(
"{ \"a\" : \"b\" ]");
cxstring text4 = cx_str(
"{ \"name\": \"value\" ]");
cxstring tests[] = { text0, text1, text2, text3, text4 };
CxJsonStatus errors[] = {
CX_JSON_FORMAT_ERROR_NUMBER,
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN,
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN,
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN,
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN
};
CX_TEST_DO {
CxJsonStatus result;
CxJson json;
CxJsonValue *obj =
NULL;
for(
int i=
0;i<
5;i++) {
cxJsonInit(&json,
NULL);
cxJsonFill(&json, tests[i]);
result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result == errors[i]);
CX_TEST_ASSERT(obj !=
NULL && obj->type ==
CX_JSON_NOTHING);
cxJsonDestroy(&json);
}
}
}
CX_TEST(test_json_object_remove_member) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
const CxAllocator *alloc = &talloc.base;
CX_TEST_DO {
CxJson json;
cxJsonInit(&json, alloc);
cxJsonFill(&json,
"{\n"
"\t\"message\":\"success\",\n"
"\t\"data\":{\n"
"\t\t\"obj\":{\n"
"\t\t\t\"array\": [1, 2, 3]\n"
"\t\t}\n"
"\t},\n"
"\t\"timestamp\":1729348561\n"
"}"
);
CxJsonValue *obj;
CX_TEST_ASSERT(
CX_JSON_NO_ERROR == cxJsonNext(&json, &obj));
cxJsonDestroy(&json);
CX_TEST_ASSERT(cxJsonIsObject(cxJsonObjGet(obj,
"data")));
CxJsonValue *data = cxJsonObjRemove(obj,
"data");
CX_TEST_ASSERT(cxJsonIsObject(data));
CX_TEST_ASSERT(!cxJsonIsObject(cxJsonObjGet(obj,
"data")));
CX_TEST_ASSERT(cxJsonIsObject(cxJsonObjGet(data,
"obj")));
CX_TEST_ASSERT(
NULL == cxJsonObjRemove(obj,
"data"));
cxJsonValueFree(obj);
CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc));
cxJsonValueFree(data);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_json_large_nesting_depth) {
CxJson json;
CxJsonValue *d1;
cxstring text = cx_str(
"{\"test\": [{},{\"foo\": [[{\"bar\":[4, 2, [null, {\"key\": 47}]]}]]}]}");
CX_TEST_DO {
cxJsonInit(&json,
NULL);
cxJsonFill(&json, text);
cxJsonNext(&json, &d1);
CX_TEST_ASSERT(d1 !=
NULL);
CX_TEST_ASSERT(cxJsonIsObject(d1));
CxJsonValue *d2 = cxJsonObjGet(d1,
"test");
CX_TEST_ASSERT(cxJsonIsArray(d2));
CX_TEST_ASSERT(cxJsonArrSize(d2) ==
2);
CxJsonValue *d3 = cxJsonArrGet(d2,
1);
CX_TEST_ASSERT(cxJsonIsObject(d3));
CxJsonValue *d4 = cxJsonObjGet(d3,
"foo");
CX_TEST_ASSERT(cxJsonIsArray(d4));
CX_TEST_ASSERT(cxJsonArrSize(d4) ==
1);
CxJsonValue *d5 = cxJsonArrGet(d4,
0);
CX_TEST_ASSERT(cxJsonIsArray(d5));
CX_TEST_ASSERT(cxJsonArrSize(d5) ==
1);
CxJsonValue *d6 = cxJsonArrGet(d5,
0);
CX_TEST_ASSERT(cxJsonIsObject(d6));
CxJsonValue *d7 = cxJsonObjGet(d6,
"bar");
CX_TEST_ASSERT(cxJsonIsArray(d7));
CX_TEST_ASSERT(cxJsonArrSize(d7) ==
3);
CxJsonValue *d8 = cxJsonArrGet(d7,
2);
CX_TEST_ASSERT(cxJsonIsArray(d8));
CX_TEST_ASSERT(cxJsonArrSize(d8) ==
2);
CxJsonValue *d9a = cxJsonArrGet(d8,
0);
CX_TEST_ASSERT(cxJsonIsNull(d9a));
CxJsonValue *d9b = cxJsonArrGet(d8,
1);
CX_TEST_ASSERT(cxJsonIsObject(d9b));
CxJsonValue *d10 = cxJsonObjGet(d9b,
"key");
CX_TEST_ASSERT(cxJsonIsInteger(d10));
CX_TEST_ASSERT(cxJsonAsInteger(d10) ==
47);
CX_TEST_ASSERT(json.states != json.states_internal);
CX_TEST_ASSERT(json.states_capacity > cx_nmemb(json.states_internal));
cxJsonValueFree(d1);
cxJsonDestroy(&json);
}
}
CX_TEST(test_json_number) {
CxJson json;
cxJsonInit(&json,
NULL);
CX_TEST_DO {
CxJsonValue *v;
CxJsonStatus result;
cxJsonFill(&json,
"3.1415 ");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsNumber(v));
CX_TEST_ASSERT(
0 == cx_vcmp_double(cxJsonAsDouble(v),
3.1415));
cxJsonValueFree(v);
cxJsonFill(&json,
"-47.11e2 ");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsNumber(v));
CX_TEST_ASSERT(
0 == cx_vcmp_double(cxJsonAsDouble(v),
-4711.0));
cxJsonValueFree(v);
cxJsonFill(&json,
"0.815e-3 ");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsNumber(v));
CX_TEST_ASSERT(
0 == cx_vcmp_double(cxJsonAsDouble(v),
0.000815));
cxJsonValueFree(v);
cxJsonFill(&json,
"1.23E4 ");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsNumber(v));
CX_TEST_ASSERT(cxJsonAsInteger(v) ==
12300);
CX_TEST_ASSERT(cxJsonAsDouble(v) ==
12300.0);
cxJsonValueFree(v);
cxJsonFill(&json,
"18446744073709551615.0123456789 ");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsNumber(v));
CX_TEST_ASSERT(
0 == cx_vcmp_double(cxJsonAsDouble(v),
1.8446744073709552e+19));
cxJsonValueFree(v);
}
cxJsonDestroy(&json);
}
CX_TEST(test_json_number_format_errors) {
CxJson json;
cxJsonInit(&json,
NULL);
CX_TEST_DO {
CxJsonValue *v;
CxJsonStatus result;
cxJsonFill(&json,
"+3.1415 ");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERTM(result ==
CX_JSON_FORMAT_ERROR_NUMBER,
"leading plus is not RFC-8259 compliant");
CX_TEST_ASSERT(v->type ==
CX_JSON_NOTHING);
cxJsonReset(&json);
cxJsonFill(&json,
"0.815e-3.0 ");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERTM(result ==
CX_JSON_FORMAT_ERROR_NUMBER,
"exponent must be an integer");
CX_TEST_ASSERT(v->type ==
CX_JSON_NOTHING);
cxJsonReset(&json);
cxJsonFill(&json,
"3.14e ");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERTM(result ==
CX_JSON_FORMAT_ERROR_NUMBER,
"exponent cannot be empty");
CX_TEST_ASSERT(v->type ==
CX_JSON_NOTHING);
cxJsonReset(&json);
cxJsonFill(&json,
"3.14e~7 ");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERTM(result ==
CX_JSON_FORMAT_ERROR_NUMBER,
"exponent cannot start with bullshit");
CX_TEST_ASSERT(v->type ==
CX_JSON_NOTHING);
cxJsonReset(&json);
cxJsonFill(&json,
"1.23e4f ");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERTM(result ==
CX_JSON_FORMAT_ERROR_NUMBER,
"non-digits in exponent");
CX_TEST_ASSERT(v->type ==
CX_JSON_NOTHING);
cxJsonReset(&json);
cxJsonFill(&json,
"1.23f ");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERTM(result ==
CX_JSON_FORMAT_ERROR_NUMBER,
"non-digits in value");
CX_TEST_ASSERT(v->type ==
CX_JSON_NOTHING);
cxJsonReset(&json);
cxJsonFill(&json,
"1.23.45 ");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERTM(result ==
CX_JSON_FORMAT_ERROR_NUMBER,
"multiple decimal separators");
CX_TEST_ASSERT(v->type ==
CX_JSON_NOTHING);
cxJsonReset(&json);
cxJsonFill(&json,
"184467440737095516150123456789 ");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERTM(result ==
CX_JSON_FORMAT_ERROR_NUMBER,
"30 digit int does not fit into 64-bit int");
CX_TEST_ASSERT(v->type ==
CX_JSON_NOTHING);
cxJsonReset(&json);
}
cxJsonDestroy(&json);
}
CX_TEST(test_json_multiple_values) {
CxJson json;
cxJsonInit(&json,
NULL);
CX_TEST_DO {
CxJsonValue *v;
CxJsonStatus result;
cxJsonFill(&json,
"10\n");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsNumber(v));
CX_TEST_ASSERT(cxJsonAsInteger(v) ==
10);
cxJsonValueFree(v);
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_DATA);
cxJsonFill(&json,
"\"hello world\"\n");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsString(v));
CX_TEST_ASSERT(!cx_strcmp(cxJsonAsCxString(v),
"hello world"));
cxJsonValueFree(v);
cxJsonFill(&json,
"{ \"value\": \"test\" }\n");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsObject(v));
CxJsonValue *value = cxJsonObjGet(v,
"value");
CX_TEST_ASSERT(cxJsonAsString(value));
cxJsonValueFree(v);
cxJsonFill(&json,
"[ 0, 1, 2, 3, 4, 5 ]\n");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsArray(v));
CxJsonValue *a0 = cxJsonArrGet(v,
0);
CxJsonValue *a3 = cxJsonArrGet(v,
3);
CX_TEST_ASSERT(cxJsonIsNumber(a0));
CX_TEST_ASSERT(cxJsonAsInteger(a0) ==
0);
CX_TEST_ASSERT(cxJsonIsNumber(a3));
CX_TEST_ASSERT(cxJsonAsInteger(a3) ==
3);
cxJsonValueFree(v);
cxJsonFill(&json,
"true\n");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsLiteral(v));
CX_TEST_ASSERT(cxJsonIsBool(v));
CX_TEST_ASSERT(cxJsonIsTrue(v));
CX_TEST_ASSERT(cxJsonAsBool(v));
cxJsonValueFree(v);
cxJsonFill(&json,
"false\n");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsLiteral(v));
CX_TEST_ASSERT(cxJsonIsBool(v));
CX_TEST_ASSERT(cxJsonIsFalse(v));
CX_TEST_ASSERT(!cxJsonAsBool(v));
cxJsonValueFree(v);
cxJsonFill(&json,
"null\n");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsLiteral(v));
CX_TEST_ASSERT(!cxJsonIsBool(v));
CX_TEST_ASSERT(cxJsonIsNull(v));
cxJsonValueFree(v);
}
cxJsonDestroy(&json);
}
CX_TEST(test_json_array) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
const CxAllocator *allocator = &talloc.base;
CX_TEST_DO {
CxJsonValue *arr = cxJsonCreateArr(allocator);
cxJsonArrAddIntegers(arr, (
int64_t[]){
0,
3,
6,
9,
12,
15},
6);
CX_TEST_ASSERT(cxJsonArrSize(arr) ==
6);
CxJsonValue *e = cxJsonArrGet(arr,
3);
CX_TEST_ASSERT(cxJsonIsNumber(e));
CX_TEST_ASSERT(cxJsonAsInteger(e) ==
9);
CX_TEST_ASSERT(cxJsonAsDouble(e) ==
9.0);
CX_TEST_ASSERT(cxJsonArrSize(arr) ==
6);
e = cxJsonArrGet(arr,
6);
CX_TEST_ASSERT(!cxJsonIsNumber(e));
CX_TEST_ASSERT(!cxJsonIsNull(e));
CX_TEST_ASSERT(e->type ==
CX_JSON_NOTHING);
CxJsonValue *removed = cxJsonArrRemove(arr,
2);
CX_TEST_ASSERT(cxJsonIsNumber(removed));
CX_TEST_ASSERT(cxJsonAsInteger(removed) ==
6);
CX_TEST_ASSERT(cxJsonArrSize(arr) ==
5);
e = cxJsonArrGet(arr,
3);
CX_TEST_ASSERT(cxJsonIsNumber(e));
CX_TEST_ASSERT(cxJsonAsInteger(e) ==
12);
e = cxJsonArrRemove(arr,
5);
CX_TEST_ASSERT(e ==
NULL);
cxJsonValueFree(arr);
CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc));
cxJsonValueFree(removed);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_json_array_iterator) {
CxJson json;
cxJsonInit(&json,
NULL);
CX_TEST_DO {
CxJsonValue *v;
CxJsonStatus result;
cxJsonFill(&json,
"[ 0, 3, 6, 9, 12, 15 ]\n");
result = cxJsonNext(&json, &v);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(cxJsonIsArray(v));
CxIterator iter = cxJsonArrIter(v);
unsigned i =
0;
cx_foreach(CxJsonValue*, elem, iter) {
CX_TEST_ASSERT(cxJsonIsNumber(elem));
CX_TEST_ASSERT(i == cxJsonAsInteger(elem));
i +=
3;
}
cxJsonValueFree(v);
}
cxJsonDestroy(&json);
}
CX_TEST(test_json_allocator) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
CxAllocator *allocator = &talloc.base;
cxstring text = cx_str(
"{\n"
"\t\"message\":\"success\",\n"
"\t\"data\":[\"value1\",{\"x\":123, \"y\":523 }]\n"
"}"
);
CX_TEST_DO {
CxJson json;
cxJsonInit(&json, allocator);
cxJsonFill(&json, text);
CxJsonValue *obj;
CxJsonStatus result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_NO_ERROR);
CX_TEST_ASSERT(obj->allocator == allocator);
cxJsonValueFree(obj);
cxJsonDestroy(&json);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_json_allocator_parse_error) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
CxAllocator *allocator = &talloc.base;
cxstring text = cx_str(
"{\n"
"\t\"message\":\"success\"\n"
"\t\"data\":[\"value1\",{\"x\":123, \"y\":523 }]\n"
"}"
);
CX_TEST_DO {
CxJson json;
cxJsonInit(&json, allocator);
cxJsonFill(&json, text);
CxJsonValue *obj =
NULL;
CxJsonStatus result = cxJsonNext(&json, &obj);
CX_TEST_ASSERT(result ==
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
CX_TEST_ASSERT(obj !=
NULL && obj->type ==
CX_JSON_NOTHING);
cxJsonDestroy(&json);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_json_create_value) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
CxAllocator *allocator = &talloc.base;
CX_TEST_DO {
CxJsonValue *obj = cxJsonCreateObj(allocator);
CX_TEST_ASSERT(obj !=
NULL);
CX_TEST_ASSERT(cxJsonIsObject(obj));
CX_TEST_ASSERT(obj->allocator == allocator);
{
cxJsonObjPutLiteral(obj,
"bool",
CX_JSON_FALSE);
cxJsonObjPutInteger(obj,
"int",
47);
CxJsonValue *strings = cxJsonObjPutArr(obj,
"strings");
CX_TEST_ASSERT(strings !=
NULL);
CX_TEST_ASSERT(cxJsonIsArray(strings));
const char* str[] = {
"hello",
"world"};
CX_TEST_ASSERT(
0 == cxJsonArrAddStrings(strings, str,
2));
CxJsonValue *nested = cxJsonObjPutObj(obj,
"nested");
CX_TEST_ASSERT(nested !=
NULL);
CX_TEST_ASSERT(cxJsonIsObject(nested));
cxJsonObjPutString(nested,
"string",
"test");
cxJsonArrAddNumbers(cxJsonObjPutArr(nested,
"floats"),
(
double[]){
3.1415,
47.11,
8.15},
3);
cxJsonArrAddIntegers(cxJsonObjPutArr(nested,
"ints"),
(
int64_t[]){
4,
8,
15,
16,
23,
42},
6);
cxJsonArrAddLiterals(cxJsonObjPutArr(nested,
"literals"),
(CxJsonLiteral[]){
CX_JSON_TRUE,
CX_JSON_NULL,
CX_JSON_FALSE},
3);
}
{
CX_TEST_ASSERT(cxJsonIsFalse(cxJsonObjGet(obj,
"bool")));
CX_TEST_ASSERT(
47 == cxJsonAsInteger(cxJsonObjGet(obj,
"int")));
CxJsonValue *strings = cxJsonObjGet(obj,
"strings");
CX_TEST_ASSERT(cxJsonIsArray(strings));
CX_TEST_ASSERT(
2 == cxJsonArrSize(strings));
CX_TEST_ASSERT(
0 == cx_strcmp(
"hello", cxJsonAsString(cxJsonArrGet(strings,
0))));
CX_TEST_ASSERT(
0 == cx_strcmp(
"world", cxJsonAsString(cxJsonArrGet(strings,
1))));
CxJsonValue *nested = cxJsonObjGet(obj,
"nested");
CX_TEST_ASSERT(cxJsonIsObject(nested));
CX_TEST_ASSERT(
0 == strcmp(
"test", cxJsonAsString(cxJsonObjGet(nested,
"string"))));
CxJsonValue *floats = cxJsonObjGet(nested,
"floats");
CX_TEST_ASSERT(cxJsonIsArray(floats));
CX_TEST_ASSERT(
3 == cxJsonArrSize(floats));
CX_TEST_ASSERT(
3.1415 == cxJsonAsDouble(cxJsonArrGet(floats,
0)));
CX_TEST_ASSERT(
47.11 == cxJsonAsDouble(cxJsonArrGet(floats,
1)));
CX_TEST_ASSERT(
8.15 == cxJsonAsDouble(cxJsonArrGet(floats,
2)));
CxJsonValue *ints = cxJsonObjGet(nested,
"ints");
CX_TEST_ASSERT(cxJsonIsArray(ints));
CX_TEST_ASSERT(
6 == cxJsonArrSize(ints));
CX_TEST_ASSERT(
4 == cxJsonAsInteger(cxJsonArrGet(ints,
0)));
CX_TEST_ASSERT(
8 == cxJsonAsInteger(cxJsonArrGet(ints,
1)));
CX_TEST_ASSERT(
15 == cxJsonAsInteger(cxJsonArrGet(ints,
2)));
CX_TEST_ASSERT(
16 == cxJsonAsInteger(cxJsonArrGet(ints,
3)));
CX_TEST_ASSERT(
23 == cxJsonAsInteger(cxJsonArrGet(ints,
4)));
CX_TEST_ASSERT(
42 == cxJsonAsInteger(cxJsonArrGet(ints,
5)));
CxJsonValue *literals = cxJsonObjGet(nested,
"literals");
CX_TEST_ASSERT(cxJsonIsArray(literals));
CX_TEST_ASSERT(
3 == cxJsonArrSize(literals));
CX_TEST_ASSERT(cxJsonIsTrue(cxJsonArrGet(literals,
0)));
CX_TEST_ASSERT(cxJsonIsNull(cxJsonArrGet(literals,
1)));
CX_TEST_ASSERT(cxJsonIsFalse(cxJsonArrGet(literals,
2)));
}
cxJsonValueFree(obj);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_json_overwrite_value) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
CxAllocator *allocator = &talloc.base;
CX_TEST_DO {
CxJsonValue *obj = cxJsonCreateObj(allocator);
cxJsonObjPutInteger(obj,
"test1",
1);
cxJsonObjPutInteger(obj,
"test2",
2);
cxJsonObjPutInteger(obj,
"test3",
3);
cxJsonObjPutInteger(obj,
"test2",
0);
CxMapIterator iter = cxJsonObjIter(obj);
bool found[
5] = {
0};
cx_foreach(CxMapEntry *, ov, iter) {
CxJsonValue *v = ov->value;
CX_TEST_ASSERT(cxJsonIsInteger(v));
int64_t i = cxJsonAsInteger(v);
CX_TEST_ASSERT(i >=
0 && i <=
4);
found[i] = true;
}
CX_TEST_ASSERT(found[
0]);
CX_TEST_ASSERT(found[
1]);
CX_TEST_ASSERT(!found[
2]);
CX_TEST_ASSERT(found[
3]);
CX_TEST_ASSERT(!found[
4]);
cxJsonValueFree(obj);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST_SUBROUTINE(test_json_write_sub,
const CxAllocator *allocator,
cxstring expected,
const CxJsonWriter *writer
) {
CxJsonValue *obj = cxJsonCreateObj(allocator);
cxJsonObjPutLiteral(obj,
"bool",
CX_JSON_FALSE);
cxJsonObjPutNumber(obj,
"int",
47);
CxJsonValue *strings = cxJsonObjPutArr(obj,
"strings");
cxJsonArrAddCxStrings(strings, (cxstring[]) {
CX_STR(
"hello"),
CX_STR(
"world")},
2);
CxJsonValue *nested = cxJsonObjPutObj(obj,
"nested");
CxJsonValue *objects = cxJsonObjPutArr(nested,
"objects");
CxJsonValue *obj_in_arr[
2] = {cxJsonCreateObj(allocator), cxJsonCreateObj(allocator)};
cxJsonObjPutInteger(obj_in_arr[
0],
"name1",
1);
cxJsonObjPutInteger(obj_in_arr[
0],
"name2",
3);
cxJsonObjPutInteger(obj_in_arr[
1],
"name2",
7);
cxJsonObjPutInteger(obj_in_arr[
1],
"name1",
3);
cxJsonArrAddValues(objects, obj_in_arr,
2);
cxJsonArrAddNumbers(cxJsonObjPutArr(nested,
"floats"),
(
double[]){
3.1415,
47.11,
8.15},
3);
cxJsonArrAddLiterals(cxJsonObjPutArr(nested,
"literals"),
(CxJsonLiteral[]){
CX_JSON_TRUE,
CX_JSON_NULL,
CX_JSON_FALSE},
3);
CxJsonValue *ints = cxJsonObjPutArr(nested,
"ints");
cxJsonArrAddIntegers(ints, (
int64_t[]){
4,
8,
15},
3);
CxJsonValue *nested_array = cxJsonCreateArr(allocator);
cxJsonArrAddValues(ints, &nested_array,
1);
cxJsonArrAddIntegers(nested_array, (
int64_t[]){
16,
23},
2);
cxJsonArrAddIntegers(ints, (
int64_t[]){
42},
1);
CxBuffer buf;
cxBufferInit(&buf,
NULL,
512,
NULL,
CX_BUFFER_DEFAULT);
int result = cxJsonWrite(&buf, obj, cxBufferWriteFunc, writer);
cxBufferTerminate(&buf);
CX_TEST_ASSERT(result ==
0);
CX_TEST_ASSERT(
0 == cx_strcmp(cx_strn(buf.space, buf.size), expected));
cxBufferDestroy(&buf);
cxJsonValueFree(obj);
}
CX_TEST(test_json_write_default_format) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
CxAllocator *allocator = &talloc.base;
CX_TEST_DO {
cxstring expected = cx_str(
"{\"bool\":false,"
"\"int\":47,"
"\"strings\":[\"hello\",\"world\"],"
"\"nested\":{"
"\"objects\":[{"
"\"name1\":1,"
"\"name2\":3"
"},{"
"\"name2\":7,"
"\"name1\":3"
"}],"
"\"floats\":[3.1415,47.11,8.15],"
"\"literals\":[true,null,false],"
"\"ints\":[4,8,15,[16,23],42]"
"}"
"}"
);
CxJsonWriter writer = cxJsonWriterCompact();
CX_TEST_CALL_SUBROUTINE(test_json_write_sub, allocator, expected, &writer);
CX_TEST_CALL_SUBROUTINE(test_json_write_sub, allocator, expected,
NULL);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_json_write_pretty_default_spaces) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
CxAllocator *allocator = &talloc.base;
CX_TEST_DO {
cxstring expected = cx_str(
"{\n"
" \"bool\": false,\n"
" \"int\": 47,\n"
" \"strings\": [\"hello\", \"world\"],\n"
" \"nested\": {\n"
" \"objects\": [{\n"
" \"name1\": 1,\n"
" \"name2\": 3\n"
" }, {\n"
" \"name2\": 7,\n"
" \"name1\": 3\n"
" }],\n"
" \"floats\": [3.1415, 47.11, 8.15],\n"
" \"literals\": [true, null, false],\n"
" \"ints\": [4, 8, 15, [16, 23], 42]\n"
" }\n"
"}"
);
CxJsonWriter writer = cxJsonWriterPretty(true);
CX_TEST_CALL_SUBROUTINE(test_json_write_sub, allocator, expected, &writer);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_json_write_pretty_default_tabs) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
CxAllocator *allocator = &talloc.base;
CX_TEST_DO {
cxstring expected = cx_str(
"{\n"
"\t\"bool\": false,\n"
"\t\"int\": 47,\n"
"\t\"strings\": [\"hello\", \"world\"],\n"
"\t\"nested\": {\n"
"\t\t\"objects\": [{\n"
"\t\t\t\"name1\": 1,\n"
"\t\t\t\"name2\": 3\n"
"\t\t}, {\n"
"\t\t\t\"name2\": 7,\n"
"\t\t\t\"name1\": 3\n"
"\t\t}],\n"
"\t\t\"floats\": [3.1415, 47.11, 8.15],\n"
"\t\t\"literals\": [true, null, false],\n"
"\t\t\"ints\": [4, 8, 15, [16, 23], 42]\n"
"\t}\n"
"}"
);
CxJsonWriter writer = cxJsonWriterPretty(false);
CX_TEST_CALL_SUBROUTINE(test_json_write_sub, allocator, expected, &writer);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_json_write_pretty_deep_nesting) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
CxAllocator *allocator = &talloc.base;
CX_TEST_DO {
cxstring expected = cx_str(
"{\n"
" \"test\": {\n"
" \"test\": {\n"
" \"test\": {\n"
" \"test\": {\n"
" \"test\": {\n"
" \"test\": 47\n"
" }\n"
" }\n"
" }\n"
" }\n"
" }\n"
"}"
);
CxJsonValue *obj = cxJsonCreateObj(allocator);
CxJsonValue *test = obj;
for (
unsigned i =
0 ; i <
5 ; i++) {
test = cxJsonObjPutObj(test,
"test");
}
cxJsonObjPutInteger(test,
"test",
47);
CxJsonWriter writer = cxJsonWriterPretty(true);
writer.indent =
8;
CxBuffer buf;
cxBufferInit(&buf,
NULL,
512,
NULL,
CX_BUFFER_DEFAULT);
int result = cxJsonWrite(&buf, obj, cxBufferWriteFunc, &writer);
cxBufferTerminate(&buf);
CX_TEST_ASSERT(result ==
0);
CX_TEST_ASSERT(
0 == cx_strcmp(cx_strn(buf.space, buf.size), expected));
cxBufferDestroy(&buf);
cxJsonValueFree(obj);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_json_write_frac_max_digits) {
CxJsonValue* num = cxJsonCreateNumber(
NULL,
3.141592653589793);
CxJsonWriter writer = cxJsonWriterCompact();
CxBuffer buf;
cxBufferInit(&buf,
NULL,
32,
NULL,
0);
CX_TEST_DO {
CX_TEST_ASSERT(
0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer));
CX_TEST_ASSERT(
0 == cx_strcmp(cx_strn(buf.space, buf.size),
"3.141592"));
cxBufferReset(&buf);
writer.frac_max_digits =
200;
CX_TEST_ASSERT(
0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer));
CX_TEST_ASSERT(
0 == cx_strcmp(cx_strn(buf.space, buf.size),
"3.141592653589793"));
cxBufferReset(&buf);
writer.frac_max_digits =
0;
CX_TEST_ASSERT(
0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer));
CX_TEST_ASSERT(
0 == cx_strcmp(cx_strn(buf.space, buf.size),
"3"));
cxBufferReset(&buf);
writer.frac_max_digits =
2;
CX_TEST_ASSERT(
0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer));
CX_TEST_ASSERT(
0 == cx_strcmp(cx_strn(buf.space, buf.size),
"3.14"));
cxBufferReset(&buf);
writer.frac_max_digits =
3;
CX_TEST_ASSERT(
0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer));
CX_TEST_ASSERT(
0 == cx_strcmp(cx_strn(buf.space, buf.size),
"3.141"));
num->number =
47.110815;
cxBufferReset(&buf);
writer.frac_max_digits =
6;
CX_TEST_ASSERT(
0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer));
CX_TEST_ASSERT(
0 == cx_strcmp(cx_strn(buf.space, buf.size),
"47.110815"));
num->number =
5.11223344e23;
cxBufferReset(&buf);
writer.frac_max_digits =
4;
CX_TEST_ASSERT(
0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer));
CX_TEST_ASSERT(
0 == cx_strcmp(cx_strn(buf.space, buf.size),
"5.1122e+23"));
}
cxBufferDestroy(&buf);
cxJsonValueFree(num);
}
CX_TEST(test_json_write_string_escape) {
CxJsonValue* str = cxJsonCreateString(
NULL,
"hello\twörld\r\nthis is\\a \"string\"\b in \a string\f");
CxJsonWriter writer = cxJsonWriterCompact();
CxBuffer buf;
cxBufferInit(&buf,
NULL,
128,
NULL,
0);
CX_TEST_DO {
CX_TEST_ASSERT(
0 == cxJsonWrite(&buf, str, cxBufferWriteFunc, &writer));
CX_TEST_ASSERT(
0 == cx_strcmp(cx_strn(buf.space, buf.size),
"\"hello\\twörld\\r\\nthis is\\\\a \\\"string\\\"\\b in \\u0007 string\\f\""));
}
cxBufferDestroy(&buf);
cxJsonValueFree(str);
}
CX_TEST(test_json_write_name_escape) {
CxJsonValue* obj = cxJsonCreateObj(
NULL);
cxJsonObjPutLiteral(obj,
"hello\twörld\r\nthis is\\a \"string\"\b in \a string\f",
CX_JSON_TRUE);
CxJsonWriter writer = cxJsonWriterCompact();
CxBuffer buf;
cxBufferInit(&buf,
NULL,
128,
NULL,
0);
CX_TEST_DO {
CX_TEST_ASSERT(
0 == cxJsonWrite(&buf, obj, cxBufferWriteFunc, &writer));
CX_TEST_ASSERT(
0 == cx_strcmp(cx_strn(buf.space, buf.size),
"{\"hello\\twörld\\r\\nthis is\\\\a \\\"string\\\"\\b in \\u0007 string\\f\":true}"));
}
cxBufferDestroy(&buf);
cxJsonValueFree(obj);
}
CX_TEST(test_json_write_solidus) {
CxJsonValue* str = cxJsonCreateString(
NULL,
"test/solidus");
CxJsonWriter writer = cxJsonWriterCompact();
CxBuffer buf;
cxBufferInit(&buf,
NULL,
16,
NULL,
0);
CX_TEST_DO {
CX_TEST_ASSERT(
0 == cxJsonWrite(&buf, str, cxBufferWriteFunc, &writer));
CX_TEST_ASSERT(
0 == cx_strcmp(cx_strn(buf.space, buf.size),
"\"test/solidus\""));
writer.escape_slash = true;
cxBufferReset(&buf);
CX_TEST_ASSERT(
0 == cxJsonWrite(&buf, str, cxBufferWriteFunc, &writer));
CX_TEST_ASSERT(
0 == cx_strcmp(cx_strn(buf.space, buf.size),
"\"test\\/solidus\""));
}
cxBufferDestroy(&buf);
cxJsonValueFree(str);
}
CX_TEST(test_json_write_nothing) {
CxBuffer buf;
cxBufferInit(&buf,
NULL,
16,
NULL,
0);
CX_TEST_DO {
CxJsonValue nothing;
nothing.type =
CX_JSON_NOTHING;
CX_TEST_ASSERT(
0 == cxJsonWrite(&buf, ¬hing, cxBufferWriteFunc,
NULL));
CX_TEST_ASSERT(buf.size ==
0);
}
cxBufferDestroy(&buf);
}
CxTestSuite *cx_test_suite_json(
void) {
CxTestSuite *suite = cx_test_suite_new(
"json");
cx_test_register(suite, test_json_init_default);
cx_test_register(suite, test_json_simple_object);
cx_test_register(suite, test_json_large_object);
cx_test_register(suite, test_json_from_string);
cx_test_register(suite, test_json_from_string_errors);
cx_test_register(suite, test_json_from_string_multiple_values);
cx_test_register(suite, test_json_from_string_untrimmed);
cx_test_register(suite, test_json_escaped_strings);
cx_test_register(suite, test_json_escaped_unicode_strings);
cx_test_register(suite, test_json_escaped_unicode_malformed);
cx_test_register(suite, test_json_escaped_end_of_string);
cx_test_register(suite, test_json_object_incomplete_token);
cx_test_register(suite, test_json_parenthesis_mismatch);
cx_test_register(suite, test_json_object_name_is_no_string);
cx_test_register(suite, test_json_token_wrongly_completed);
cx_test_register(suite, test_json_object_error);
cx_test_register(suite, test_json_object_remove_member);
cx_test_register(suite, test_json_subsequent_fill);
cx_test_register(suite, test_json_no_fill);
cx_test_register(suite, test_json_null_fill);
cx_test_register(suite, test_json_large_nesting_depth);
cx_test_register(suite, test_json_number);
cx_test_register(suite, test_json_number_format_errors);
cx_test_register(suite, test_json_multiple_values);
cx_test_register(suite, test_json_array);
cx_test_register(suite, test_json_array_iterator);
cx_test_register(suite, test_json_allocator);
cx_test_register(suite, test_json_allocator_parse_error);
cx_test_register(suite, test_json_create_value);
cx_test_register(suite, test_json_overwrite_value);
cx_test_register(suite, test_json_write_default_format);
cx_test_register(suite, test_json_write_pretty_default_spaces);
cx_test_register(suite, test_json_write_pretty_default_tabs);
cx_test_register(suite, test_json_write_pretty_deep_nesting);
cx_test_register(suite, test_json_write_frac_max_digits);
cx_test_register(suite, test_json_write_string_escape);
cx_test_register(suite, test_json_write_name_escape);
cx_test_register(suite, test_json_write_solidus);
cx_test_register(suite, test_json_write_nothing);
return suite;
}