mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 23:24:09 +00:00
622 lines
22 KiB
C
622 lines
22 KiB
C
/*
|
|
Copyright: Boaz Segev, 2017-2019
|
|
License: MIT
|
|
*/
|
|
#include <fiobj_json.h>
|
|
#define FIO_ARY_NAME fio_json_stack
|
|
#define FIO_ARY_TYPE FIOBJ
|
|
#include <fio.h>
|
|
|
|
#include <fio_json_parser.h>
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* *****************************************************************************
|
|
JSON API
|
|
***************************************************************************** */
|
|
|
|
/**
|
|
* Parses JSON, setting `pobj` to point to the new Object.
|
|
*
|
|
* Returns the number of bytes consumed. On Error, 0 is returned and no data is
|
|
* consumed.
|
|
*/
|
|
size_t fiobj_json2obj(FIOBJ *pobj, const void *data, size_t len);
|
|
/* Formats an object into a JSON string. Remember to `fiobj_free`. */
|
|
FIOBJ fiobj_obj2json(FIOBJ, uint8_t);
|
|
|
|
/* *****************************************************************************
|
|
FIOBJ Parser
|
|
***************************************************************************** */
|
|
|
|
typedef struct {
|
|
json_parser_s p;
|
|
FIOBJ key;
|
|
FIOBJ top;
|
|
FIOBJ target;
|
|
fio_json_stack_s stack;
|
|
uint8_t is_hash;
|
|
} fiobj_json_parser_s;
|
|
|
|
/* *****************************************************************************
|
|
FIOBJ Callacks
|
|
***************************************************************************** */
|
|
|
|
static inline void fiobj_json_add2parser(fiobj_json_parser_s *p, FIOBJ o) {
|
|
if (p->top) {
|
|
if (p->is_hash) {
|
|
if (p->key) {
|
|
fiobj_hash_set(p->top, p->key, o);
|
|
fiobj_free(p->key);
|
|
p->key = FIOBJ_INVALID;
|
|
} else {
|
|
p->key = o;
|
|
}
|
|
} else {
|
|
fiobj_ary_push(p->top, o);
|
|
}
|
|
} else {
|
|
p->top = o;
|
|
}
|
|
}
|
|
|
|
/** a NULL object was detected */
|
|
static void fio_json_on_null(json_parser_s *p) {
|
|
fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_null());
|
|
}
|
|
/** a TRUE object was detected */
|
|
static void fio_json_on_true(json_parser_s *p) {
|
|
fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_true());
|
|
}
|
|
/** a FALSE object was detected */
|
|
static void fio_json_on_false(json_parser_s *p) {
|
|
fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_false());
|
|
}
|
|
/** a Numberl was detected (long long). */
|
|
static void fio_json_on_number(json_parser_s *p, long long i) {
|
|
fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_num_new(i));
|
|
}
|
|
/** a Float was detected (double). */
|
|
static void fio_json_on_float(json_parser_s *p, double f) {
|
|
fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_float_new(f));
|
|
}
|
|
/** a String was detected (int / float). update `pos` to point at ending */
|
|
static void fio_json_on_string(json_parser_s *p, void *start, size_t length) {
|
|
FIOBJ str = fiobj_str_buf(length);
|
|
fiobj_str_resize(
|
|
str, fio_json_unescape_str(fiobj_obj2cstr(str).data, start, length));
|
|
fiobj_json_add2parser((fiobj_json_parser_s *)p, str);
|
|
}
|
|
/** a dictionary object was detected */
|
|
static int fio_json_on_start_object(json_parser_s *p) {
|
|
fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
|
|
if (pr->target) {
|
|
/* push NULL, don't free the objects */
|
|
fio_json_stack_push(&pr->stack, pr->top);
|
|
pr->top = pr->target;
|
|
pr->target = FIOBJ_INVALID;
|
|
} else {
|
|
FIOBJ hash = fiobj_hash_new();
|
|
fiobj_json_add2parser(pr, hash);
|
|
fio_json_stack_push(&pr->stack, pr->top);
|
|
pr->top = hash;
|
|
}
|
|
pr->is_hash = 1;
|
|
return 0;
|
|
}
|
|
/** a dictionary object closure detected */
|
|
static void fio_json_on_end_object(json_parser_s *p) {
|
|
fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
|
|
if (pr->key) {
|
|
FIO_LOG_WARNING("(JSON parsing) malformed JSON, "
|
|
"ignoring dangling Hash key.");
|
|
fiobj_free(pr->key);
|
|
pr->key = FIOBJ_INVALID;
|
|
}
|
|
pr->top = FIOBJ_INVALID;
|
|
fio_json_stack_pop(&pr->stack, &pr->top);
|
|
pr->is_hash = FIOBJ_TYPE_IS(pr->top, FIOBJ_T_HASH);
|
|
}
|
|
/** an array object was detected */
|
|
static int fio_json_on_start_array(json_parser_s *p) {
|
|
fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
|
|
if (pr->target)
|
|
return -1;
|
|
FIOBJ ary = fiobj_ary_new();
|
|
fiobj_json_add2parser(pr, ary);
|
|
fio_json_stack_push(&pr->stack, pr->top);
|
|
pr->top = ary;
|
|
pr->is_hash = 0;
|
|
return 0;
|
|
}
|
|
/** an array closure was detected */
|
|
static void fio_json_on_end_array(json_parser_s *p) {
|
|
fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
|
|
pr->top = FIOBJ_INVALID;
|
|
fio_json_stack_pop(&pr->stack, &pr->top);
|
|
pr->is_hash = FIOBJ_TYPE_IS(pr->top, FIOBJ_T_HASH);
|
|
}
|
|
/** the JSON parsing is complete */
|
|
static void fio_json_on_json(json_parser_s *p) {
|
|
// fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
|
|
// FIO_ARY_FOR(&pr->stack, pos) { fiobj_free((FIOBJ)pos.obj); }
|
|
// fio_json_stack_free(&pr->stack);
|
|
(void)p; /* nothing special... right? */
|
|
}
|
|
/** the JSON parsing is complete */
|
|
static void fio_json_on_error(json_parser_s *p) {
|
|
fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
|
|
#if DEBUG
|
|
FIO_LOG_DEBUG("JSON on error called.");
|
|
#endif
|
|
fiobj_free((FIOBJ)fio_json_stack_get(&pr->stack, 0));
|
|
fiobj_free(pr->key);
|
|
fio_json_stack_free(&pr->stack);
|
|
*pr = (fiobj_json_parser_s){.top = FIOBJ_INVALID};
|
|
}
|
|
|
|
/* *****************************************************************************
|
|
JSON formatting
|
|
***************************************************************************** */
|
|
|
|
/** Writes a JSON friendly version of the src String */
|
|
static void write_safe_str(FIOBJ dest, const FIOBJ str) {
|
|
fio_str_info_s s = fiobj_obj2cstr(str);
|
|
fio_str_info_s t = fiobj_obj2cstr(dest);
|
|
t.data[t.len] = '"';
|
|
t.len++;
|
|
fiobj_str_resize(dest, t.len);
|
|
t = fiobj_obj2cstr(dest);
|
|
const uint8_t *restrict src = (const uint8_t *)s.data;
|
|
size_t len = s.len;
|
|
uint64_t end = t.len;
|
|
/* make sure we have some room */
|
|
size_t added = 0;
|
|
size_t capa = fiobj_str_capa(dest);
|
|
if (capa <= end + s.len + 64) {
|
|
if (0) {
|
|
capa = (((capa >> 12) + 1) << 12) - 1;
|
|
capa = fiobj_str_capa_assert(dest, capa);
|
|
} else {
|
|
capa = fiobj_str_capa_assert(dest, (end + s.len + 64));
|
|
}
|
|
fio_str_info_s tmp = fiobj_obj2cstr(dest);
|
|
t = tmp;
|
|
}
|
|
while (len) {
|
|
char *restrict writer = (char *)t.data;
|
|
while (len && src[0] > 32 && src[0] != '"' && src[0] != '\\') {
|
|
len--;
|
|
writer[end++] = *(src++);
|
|
}
|
|
if (!len)
|
|
break;
|
|
switch (src[0]) {
|
|
case '\b':
|
|
writer[end++] = '\\';
|
|
writer[end++] = 'b';
|
|
added++;
|
|
break; /* from switch */
|
|
case '\f':
|
|
writer[end++] = '\\';
|
|
writer[end++] = 'f';
|
|
added++;
|
|
break; /* from switch */
|
|
case '\n':
|
|
writer[end++] = '\\';
|
|
writer[end++] = 'n';
|
|
added++;
|
|
break; /* from switch */
|
|
case '\r':
|
|
writer[end++] = '\\';
|
|
writer[end++] = 'r';
|
|
added++;
|
|
break; /* from switch */
|
|
case '\t':
|
|
writer[end++] = '\\';
|
|
writer[end++] = 't';
|
|
added++;
|
|
break; /* from switch */
|
|
case '"':
|
|
case '\\':
|
|
case '/':
|
|
writer[end++] = '\\';
|
|
writer[end++] = src[0];
|
|
added++;
|
|
break; /* from switch */
|
|
default:
|
|
if (src[0] <= 31) {
|
|
/* MUST escape all control values less than 32 */
|
|
writer[end++] = '\\';
|
|
writer[end++] = 'u';
|
|
writer[end++] = '0';
|
|
writer[end++] = '0';
|
|
writer[end++] = hex_chars[src[0] >> 4];
|
|
writer[end++] = hex_chars[src[0] & 15];
|
|
added += 4;
|
|
} else
|
|
writer[end++] = src[0];
|
|
break; /* from switch */
|
|
}
|
|
src++;
|
|
len--;
|
|
if (added >= 48 && capa <= end + len + 64) {
|
|
writer[end] = 0;
|
|
fiobj_str_resize(dest, end);
|
|
fiobj_str_capa_assert(dest, (end + len + 64));
|
|
t = fiobj_obj2cstr(dest);
|
|
writer = (char *)t.data;
|
|
capa = t.capa;
|
|
added = 0;
|
|
}
|
|
}
|
|
t.data[end++] = '"';
|
|
fiobj_str_resize(dest, end);
|
|
}
|
|
|
|
typedef struct {
|
|
FIOBJ dest;
|
|
FIOBJ parent;
|
|
fio_json_stack_s *stack;
|
|
uintptr_t count;
|
|
uint8_t pretty;
|
|
} obj2json_data_s;
|
|
|
|
static int fiobj_obj2json_task(FIOBJ o, void *data_) {
|
|
obj2json_data_s *data = data_;
|
|
uint8_t add_seperator = 1;
|
|
if (fiobj_hash_key_in_loop()) {
|
|
write_safe_str(data->dest, fiobj_hash_key_in_loop());
|
|
fiobj_str_write(data->dest, ":", 1);
|
|
}
|
|
switch (FIOBJ_TYPE(o)) {
|
|
case FIOBJ_T_NUMBER:
|
|
case FIOBJ_T_NULL:
|
|
case FIOBJ_T_TRUE:
|
|
case FIOBJ_T_FALSE:
|
|
case FIOBJ_T_FLOAT:
|
|
fiobj_str_join(data->dest, o);
|
|
--data->count;
|
|
break;
|
|
|
|
case FIOBJ_T_DATA:
|
|
case FIOBJ_T_UNKNOWN:
|
|
case FIOBJ_T_STRING:
|
|
write_safe_str(data->dest, o);
|
|
--data->count;
|
|
break;
|
|
|
|
case FIOBJ_T_ARRAY:
|
|
--data->count;
|
|
fio_json_stack_push(data->stack, data->parent);
|
|
fio_json_stack_push(data->stack, (FIOBJ)data->count);
|
|
data->parent = o;
|
|
data->count = fiobj_ary_count(o);
|
|
fiobj_str_write(data->dest, "[", 1);
|
|
add_seperator = 0;
|
|
break;
|
|
|
|
case FIOBJ_T_HASH:
|
|
--data->count;
|
|
fio_json_stack_push(data->stack, data->parent);
|
|
fio_json_stack_push(data->stack, (FIOBJ)data->count);
|
|
data->parent = o;
|
|
data->count = fiobj_hash_count(o);
|
|
fiobj_str_write(data->dest, "{", 1);
|
|
add_seperator = 0;
|
|
break;
|
|
}
|
|
if (data->pretty) {
|
|
fiobj_str_capa_assert(data->dest,
|
|
fiobj_obj2cstr(data->dest).len +
|
|
(fio_json_stack_count(data->stack) * 5));
|
|
while (!data->count && data->parent) {
|
|
if (FIOBJ_TYPE_IS(data->parent, FIOBJ_T_HASH)) {
|
|
fiobj_str_write(data->dest, "}", 1);
|
|
} else {
|
|
fiobj_str_write(data->dest, "]", 1);
|
|
}
|
|
add_seperator = 1;
|
|
data->count = 0;
|
|
fio_json_stack_pop(data->stack, &data->count);
|
|
data->parent = FIOBJ_INVALID;
|
|
fio_json_stack_pop(data->stack, &data->parent);
|
|
}
|
|
|
|
if (add_seperator && data->parent) {
|
|
fiobj_str_write(data->dest, ",\n", 2);
|
|
uintptr_t indent = fio_json_stack_count(data->stack) - 1;
|
|
fiobj_str_capa_assert(data->dest,
|
|
fiobj_obj2cstr(data->dest).len + (indent * 2));
|
|
fio_str_info_s buf = fiobj_obj2cstr(data->dest);
|
|
while (indent--) {
|
|
buf.data[buf.len++] = ' ';
|
|
buf.data[buf.len++] = ' ';
|
|
}
|
|
fiobj_str_resize(data->dest, buf.len);
|
|
}
|
|
} else {
|
|
fiobj_str_capa_assert(data->dest,
|
|
fiobj_obj2cstr(data->dest).len +
|
|
(fio_json_stack_count(data->stack) << 1));
|
|
while (!data->count && data->parent) {
|
|
if (FIOBJ_TYPE_IS(data->parent, FIOBJ_T_HASH)) {
|
|
fiobj_str_write(data->dest, "}", 1);
|
|
} else {
|
|
fiobj_str_write(data->dest, "]", 1);
|
|
}
|
|
add_seperator = 1;
|
|
data->count = 0;
|
|
data->parent = FIOBJ_INVALID;
|
|
fio_json_stack_pop(data->stack, &data->count);
|
|
fio_json_stack_pop(data->stack, &data->parent);
|
|
}
|
|
|
|
if (add_seperator && data->parent) {
|
|
fiobj_str_write(data->dest, ",", 1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* *****************************************************************************
|
|
FIOBJ API
|
|
***************************************************************************** */
|
|
|
|
/**
|
|
* Parses JSON, setting `pobj` to point to the new Object.
|
|
*
|
|
* Returns the number of bytes consumed. On Error, 0 is returned and no data is
|
|
* consumed.
|
|
*/
|
|
size_t fiobj_json2obj(FIOBJ *pobj, const void *data, size_t len) {
|
|
fiobj_json_parser_s p = {.top = FIOBJ_INVALID};
|
|
size_t consumed = fio_json_parse(&p.p, data, len);
|
|
if (!consumed || p.p.depth) {
|
|
fiobj_free(fio_json_stack_get(&p.stack, 0));
|
|
p.top = FIOBJ_INVALID;
|
|
}
|
|
fio_json_stack_free(&p.stack);
|
|
fiobj_free(p.key);
|
|
*pobj = p.top;
|
|
return consumed;
|
|
}
|
|
|
|
/**
|
|
* Updates a Hash using JSON data.
|
|
*
|
|
* Parsing errors and non-dictionar object JSON data are silently ignored,
|
|
* attempting to update the Hash as much as possible before any errors
|
|
* encountered.
|
|
*
|
|
* Conflicting Hash data is overwritten (prefering the new over the old).
|
|
*
|
|
* Returns the number of bytes consumed. On Error, 0 is returned and no data is
|
|
* consumed.
|
|
*/
|
|
size_t fiobj_hash_update_json(FIOBJ hash, const void *data, size_t len) {
|
|
if (!hash)
|
|
return 0;
|
|
fiobj_json_parser_s p = {.top = FIOBJ_INVALID, .target = hash};
|
|
size_t consumed = fio_json_parse(&p.p, data, len);
|
|
fio_json_stack_free(&p.stack);
|
|
fiobj_free(p.key);
|
|
if (p.top != hash)
|
|
fiobj_free(p.top);
|
|
return consumed;
|
|
}
|
|
|
|
/**
|
|
* Formats an object into a JSON string, appending the JSON string to an
|
|
* existing String. Remember to `fiobj_free`.
|
|
*/
|
|
FIOBJ fiobj_obj2json2(FIOBJ dest, FIOBJ o, uint8_t pretty) {
|
|
assert(dest && FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING));
|
|
if (!o) {
|
|
fiobj_str_write(dest, "null", 4);
|
|
return 0;
|
|
}
|
|
fio_json_stack_s stack = FIO_ARY_INIT;
|
|
obj2json_data_s data = {
|
|
.dest = dest,
|
|
.stack = &stack,
|
|
.pretty = pretty,
|
|
.count = 1,
|
|
};
|
|
if (!o || !FIOBJ_IS_ALLOCATED(o) || !FIOBJECT2VTBL(o)->each) {
|
|
fiobj_obj2json_task(o, &data);
|
|
return dest;
|
|
}
|
|
fiobj_each2(o, fiobj_obj2json_task, &data);
|
|
fio_json_stack_free(&stack);
|
|
return dest;
|
|
}
|
|
|
|
/* Formats an object into a JSON string. Remember to `fiobj_free`. */
|
|
FIOBJ fiobj_obj2json(FIOBJ obj, uint8_t pretty) {
|
|
return fiobj_obj2json2(fiobj_str_buf(128), obj, pretty);
|
|
}
|
|
|
|
/* *****************************************************************************
|
|
Test
|
|
***************************************************************************** */
|
|
|
|
#if DEBUG
|
|
void fiobj_test_json(void) {
|
|
fprintf(stderr, "=== Testing JSON parser (simple test)\n");
|
|
#define TEST_ASSERT(cond, ...) \
|
|
if (!(cond)) { \
|
|
fprintf(stderr, "* " __VA_ARGS__); \
|
|
fprintf(stderr, "\n !!! Testing failed !!!\n"); \
|
|
exit(-1); \
|
|
}
|
|
char json_str[] = "{\"array\":[1,2,3,\"boom\"],\"my\":{\"secret\":42},"
|
|
"\"true\":true,\"false\":false,\"null\":null,\"float\":-2."
|
|
"2,\"string\":\"I \\\"wrote\\\" this.\"}";
|
|
char json_str_update[] = "{\"array\":[1,2,3]}";
|
|
char json_str2[] =
|
|
"[\n \"JSON Test Pattern pass1\",\n {\"object with 1 "
|
|
"member\":[\"array with 1 element\"]},\n {},\n [],\n -42,\n "
|
|
"true,\n false,\n null,\n {\n \"integer\": 1234567890,\n "
|
|
" \"real\": -9876.543210,\n \"e\": 0.123456789e-12,\n "
|
|
" \"E\": 1.234567890E+34,\n \"\": 23456789012E66,\n "
|
|
"\"zero\": 0,\n \"one\": 1,\n \"space\": \" \",\n "
|
|
"\"quote\": \"\\\"\",\n \"backslash\": \"\\\\\",\n "
|
|
"\"controls\": \"\\b\\f\\n\\r\\t\",\n \"slash\": \"/ & \\/\",\n "
|
|
" \"alpha\": \"abcdefghijklmnopqrstuvwyz\",\n \"ALPHA\": "
|
|
"\"ABCDEFGHIJKLMNOPQRSTUVWYZ\",\n \"digit\": \"0123456789\",\n "
|
|
" \"0123456789\": \"digit\",\n \"special\": "
|
|
"\"`1~!@#$%^&*()_+-={':[,]}|;.</>?\",\n \"hex\": "
|
|
"\"\\u0123\\u4567\\u89AB\\uCDEF\\uabcd\\uef4A\",\n \"true\": "
|
|
"true,\n \"false\": false,\n \"null\": null,\n "
|
|
"\"array\":[ ],\n \"object\":{ },\n \"address\": \"50 "
|
|
"St. James Street\",\n \"url\": \"http://www.JSON.org/\",\n "
|
|
" \"comment\": \"// /* <!-- --\",\n \"# -- --> */\": \" \",\n "
|
|
" \" s p a c e d \" :[1,2 , 3\n\n,\n\n4 , 5 , 6 "
|
|
" ,7 ],\"compact\":[1,2,3,4,5,6,7],\n \"jsontext\": "
|
|
"\"{\\\"object with 1 member\\\":[\\\"array with 1 element\\\"]}\",\n "
|
|
" \"quotes\": \"" \\u0022 %22 0x22 034 "\",\n "
|
|
"\"\\/"
|
|
"\\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t`1~!@#$"
|
|
"%^&*()_+-=[]{}|;:',./<>?\"\n: \"A key can be any string\"\n },\n "
|
|
"0.5 "
|
|
",98.6\n,\n99.44\n,\n\n1066,\n1e1,\n0.1e1,\n1e-1,\n1e00,2e+00,2e-00\n,"
|
|
"\"rosebud\"]";
|
|
|
|
FIOBJ o = 0;
|
|
TEST_ASSERT(fiobj_json2obj(&o, "1", 2) == 1,
|
|
"JSON number parsing failed to run!\n");
|
|
TEST_ASSERT(o, "JSON (single) object missing!\n");
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_NUMBER),
|
|
"JSON (single) not a number!\n");
|
|
TEST_ASSERT(fiobj_obj2num(o) == 1, "JSON (single) not == 1!\n");
|
|
fiobj_free(o);
|
|
|
|
TEST_ASSERT(fiobj_json2obj(&o, "2.0", 5) == 3,
|
|
"JSON float parsing failed to run!\n");
|
|
TEST_ASSERT(o, "JSON (float) object missing!\n");
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_FLOAT), "JSON (float) not a float!\n");
|
|
TEST_ASSERT(fiobj_obj2float(o) == 2, "JSON (float) not == 2!\n");
|
|
fiobj_free(o);
|
|
|
|
TEST_ASSERT(fiobj_json2obj(&o, json_str, sizeof(json_str)) ==
|
|
(sizeof(json_str) - 1),
|
|
"JSON parsing failed to run!\n");
|
|
TEST_ASSERT(o, "JSON object missing!\n");
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_HASH),
|
|
"JSON root not a dictionary (not a hash)!\n");
|
|
FIOBJ tmp = fiobj_hash_get2(o, fiobj_hash_string("array", 5));
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY),
|
|
"JSON 'array' not an Array!\n");
|
|
TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 0)) == 1,
|
|
"JSON 'array' index 0 error!\n");
|
|
TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 1)) == 2,
|
|
"JSON 'array' index 1 error!\n");
|
|
TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 2)) == 3,
|
|
"JSON 'array' index 2 error!\n");
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(fiobj_ary_index(tmp, 3), FIOBJ_T_STRING),
|
|
"JSON 'array' index 3 type error!\n");
|
|
TEST_ASSERT(!memcmp("boom", fiobj_obj2cstr(fiobj_ary_index(tmp, 3)).data, 4),
|
|
"JSON 'array' index 3 error!\n");
|
|
tmp = fiobj_hash_get2(o, fiobj_hash_string("my", 2));
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_HASH),
|
|
"JSON 'my:secret' not a Hash!\n");
|
|
TEST_ASSERT(
|
|
FIOBJ_TYPE_IS(fiobj_hash_get2(tmp, fiobj_hash_string("secret", 6)),
|
|
FIOBJ_T_NUMBER),
|
|
"JSON 'my:secret' doesn't hold a number!\n");
|
|
TEST_ASSERT(
|
|
fiobj_obj2num(fiobj_hash_get2(tmp, fiobj_hash_string("secret", 6))) == 42,
|
|
"JSON 'my:secret' not 42!\n");
|
|
TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("true", 4)) == fiobj_true(),
|
|
"JSON 'true' not true!\n");
|
|
TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("false", 5)) ==
|
|
fiobj_false(),
|
|
"JSON 'false' not false!\n");
|
|
TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("null", 4)) == fiobj_null(),
|
|
"JSON 'null' not null!\n");
|
|
tmp = fiobj_hash_get2(o, fiobj_hash_string("float", 5));
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_FLOAT), "JSON 'float' not a float!\n");
|
|
tmp = fiobj_hash_get2(o, fiobj_hash_string("string", 6));
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_STRING),
|
|
"JSON 'string' not a string!\n");
|
|
TEST_ASSERT(!strcmp(fiobj_obj2cstr(tmp).data, "I \"wrote\" this."),
|
|
"JSON 'string' incorrect!\n");
|
|
fprintf(stderr, "* passed.\n");
|
|
fprintf(stderr, "=== Testing JSON formatting (simple test)\n");
|
|
tmp = fiobj_obj2json(o, 0);
|
|
fprintf(stderr, "* data (%p):\n%.*s\n", (void *)fiobj_obj2cstr(tmp).data,
|
|
(int)fiobj_obj2cstr(tmp).len, fiobj_obj2cstr(tmp).data);
|
|
if (!strcmp(fiobj_obj2cstr(tmp).data, json_str))
|
|
fprintf(stderr, "* Stringify == Original.\n");
|
|
TEST_ASSERT(
|
|
fiobj_hash_update_json(o, json_str_update, strlen(json_str_update)),
|
|
"JSON update failed to parse data.");
|
|
fiobj_free(tmp);
|
|
|
|
tmp = fiobj_hash_get2(o, fiobj_hash_string("array", 5));
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY),
|
|
"JSON updated 'array' not an Array!\n");
|
|
TEST_ASSERT(fiobj_ary_count(tmp) == 3, "JSON updated 'array' not updated?");
|
|
tmp = fiobj_hash_get2(o, fiobj_hash_string("float", 5));
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_FLOAT),
|
|
"JSON updated (old) 'float' missing!\n");
|
|
fiobj_free(o);
|
|
fprintf(stderr, "* passed.\n");
|
|
|
|
fprintf(stderr, "=== Testing JSON parsing (UTF-8 and special cases)\n");
|
|
fiobj_json2obj(&o, "[\"\\uD834\\uDD1E\"]", 16);
|
|
TEST_ASSERT(o, "JSON G clef String failed to parse!\n");
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY),
|
|
"JSON G clef container has an incorrect type! (%s)\n",
|
|
fiobj_type_name(o));
|
|
tmp = o;
|
|
o = fiobj_ary_pop(o);
|
|
fiobj_free(tmp);
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING),
|
|
"JSON G clef String incorrect type! %p => %s\n", (void *)o,
|
|
fiobj_type_name(o));
|
|
TEST_ASSERT((!strcmp(fiobj_obj2cstr(o).data, "\xF0\x9D\x84\x9E")),
|
|
"JSON G clef String incorrect %s !\n", fiobj_obj2cstr(o).data);
|
|
fiobj_free(o);
|
|
|
|
fiobj_json2obj(&o, "\"\\uD834\\uDD1E\"", 14);
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING),
|
|
"JSON direct G clef String incorrect type! %p => %s\n", (void *)o,
|
|
fiobj_type_name(o));
|
|
TEST_ASSERT((!strcmp(fiobj_obj2cstr(o).data, "\xF0\x9D\x84\x9E")),
|
|
"JSON direct G clef String incorrect %s !\n",
|
|
fiobj_obj2cstr(o).data);
|
|
fiobj_free(o);
|
|
fiobj_json2obj(&o, "\"Hello\\u0000World\"", 19);
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING),
|
|
"JSON NUL containing String incorrect type! %p => %s\n",
|
|
(void *)o, fiobj_type_name(o));
|
|
TEST_ASSERT(
|
|
(!memcmp(fiobj_obj2cstr(o).data, "Hello\0World", fiobj_obj2cstr(o).len)),
|
|
"JSON NUL containing String incorrect! (%u): %s . %s\n",
|
|
(int)fiobj_obj2cstr(o).len, fiobj_obj2cstr(o).data,
|
|
fiobj_obj2cstr(o).data + 3);
|
|
fiobj_free(o);
|
|
size_t consumed = fiobj_json2obj(&o, json_str2, sizeof(json_str2));
|
|
TEST_ASSERT(
|
|
consumed == (sizeof(json_str2) - 1),
|
|
"JSON messy string failed to parse (consumed %lu instead of %lu\n",
|
|
(unsigned long)consumed, (unsigned long)(sizeof(json_str2) - 1));
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY),
|
|
"JSON messy string object error\n");
|
|
tmp = fiobj_obj2json(o, 1);
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_STRING),
|
|
"JSON messy string isn't a string\n");
|
|
fprintf(stderr, "Messy JSON:\n%s\n", fiobj_obj2cstr(tmp).data);
|
|
fiobj_free(o);
|
|
fiobj_free(tmp);
|
|
fprintf(stderr, "* passed.\n");
|
|
}
|
|
|
|
#endif
|