blob: 98e192a0ff413f0f8e5420924956942a57f0b5c6 [file] [log] [blame] [raw]
/*
Copyright: Boaz Segev, 2017-2018
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) {
if (0) {
capa = (((capa >> 12) + 1) << 12) - 1;
capa = fiobj_str_capa_assert(dest, capa);
} else {
capa = fiobj_str_capa_assert(dest, (end + len + 64));
}
t = fiobj_obj2cstr(dest);
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\": \"&#34; \\u0022 %22 0x22 034 &#x22;\",\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, fio_siphash("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, fio_siphash("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, fio_siphash("secret", 6)),
FIOBJ_T_NUMBER),
"JSON 'my:secret' doesn't hold a number!\n");
TEST_ASSERT(fiobj_obj2num(fiobj_hash_get2(tmp, fio_siphash("secret", 6))) ==
42,
"JSON 'my:secret' not 42!\n");
TEST_ASSERT(fiobj_hash_get2(o, fio_siphash("true", 4)) == fiobj_true(),
"JSON 'true' not true!\n");
TEST_ASSERT(fiobj_hash_get2(o, fio_siphash("false", 5)) == fiobj_false(),
"JSON 'false' not false!\n");
TEST_ASSERT(fiobj_hash_get2(o, fio_siphash("null", 4)) == fiobj_null(),
"JSON 'null' not null!\n");
tmp = fiobj_hash_get2(o, fio_siphash("float", 5));
TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_FLOAT), "JSON 'float' not a float!\n");
tmp = fiobj_hash_get2(o, fio_siphash("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, fio_siphash("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, fio_siphash("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