#include "cx/json.h"
#include "cx/kv_list.h"
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <inttypes.h>
#include <ctype.h>
static CxJsonValue cx_json_value_nothing = {.type =
CX_JSON_NOTHING};
static void token_destroy(CxJsonToken *token) {
if (token->allocated) {
cx_strfree(&token->content);
token->allocated = false;
}
}
static int num_isexp(
const char *content,
size_t length,
size_t pos) {
if (pos >= length) {
return 0;
}
int ok =
0;
for (
size_t i = pos; i < length; i++) {
char c = content[i];
if (isdigit((
unsigned char)c)) {
ok =
1;
}
else if (i == pos) {
if (!(c ==
'+' || c ==
'-')) {
return 0;
}
}
else {
return 0;
}
}
return ok;
}
static CxJsonTokenType token_numbertype(
const char *content,
size_t length) {
if (length ==
0)
return CX_JSON_TOKEN_ERROR;
if (content[
0] !=
'-' && !isdigit((
unsigned char)content[
0])) {
return CX_JSON_TOKEN_ERROR;
}
CxJsonTokenType type =
CX_JSON_TOKEN_INTEGER;
for (
size_t i =
1; i < length; i++) {
if (content[i] ==
'.') {
if (type ==
CX_JSON_TOKEN_NUMBER) {
return CX_JSON_TOKEN_ERROR;
}
type =
CX_JSON_TOKEN_NUMBER;
}
else if (content[i] ==
'e' || content[i] ==
'E') {
return num_isexp(content, length, i +
1) ?
CX_JSON_TOKEN_NUMBER :
CX_JSON_TOKEN_ERROR;
}
else if (!isdigit((
unsigned char)content[i])) {
return CX_JSON_TOKEN_ERROR;
}
}
return type;
}
static CxJsonToken token_create(CxJson *json, bool isstring,
size_t start,
size_t end) {
cxmutstr str = cx_mutstrn(json->buffer.space + start, end - start);
bool allocated = false;
if (json->uncompleted.tokentype !=
CX_JSON_NO_TOKEN) {
allocated = true;
str = cx_strcat_m(json->uncompleted.content,
1, str);
if (str.ptr ==
NULL) {
return (CxJsonToken){
CX_JSON_NO_TOKEN, false, {
NULL,
0}};
}
}
json->uncompleted = (CxJsonToken){
0};
CxJsonTokenType ttype;
if (isstring) {
ttype =
CX_JSON_TOKEN_STRING;
}
else {
cxstring s = cx_strcast(str);
if (!cx_strcmp(s,
CX_STR(
"true")) || !cx_strcmp(s,
CX_STR(
"false"))
|| !cx_strcmp(s,
CX_STR(
"null"))) {
ttype =
CX_JSON_TOKEN_LITERAL;
}
else {
ttype = token_numbertype(str.ptr, str.length);
}
}
if (ttype ==
CX_JSON_TOKEN_ERROR) {
if (allocated) {
cx_strfree(&str);
}
return (CxJsonToken){
CX_JSON_TOKEN_ERROR, false, {
NULL,
0}};
}
return (CxJsonToken){ttype, allocated, str};
}
static CxJsonTokenType char2ttype(
char c) {
switch (c) {
case '[': {
return CX_JSON_TOKEN_BEGIN_ARRAY;
}
case '{': {
return CX_JSON_TOKEN_BEGIN_OBJECT;
}
case ']': {
return CX_JSON_TOKEN_END_ARRAY;
}
case '}': {
return CX_JSON_TOKEN_END_OBJECT;
}
case ':': {
return CX_JSON_TOKEN_NAME_SEPARATOR;
}
case ',': {
return CX_JSON_TOKEN_VALUE_SEPARATOR;
}
case '""': {
return CX_JSON_TOKEN_STRING;
}
default: {
if (isspace((
unsigned char)c)) {
return CX_JSON_TOKEN_SPACE;
}
}
}
return CX_JSON_NO_TOKEN;
}
static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) {
if (cxBufferEof(&json->buffer)) {
return json->uncompleted.tokentype ==
CX_JSON_NO_TOKEN ?
CX_JSON_NO_DATA :
CX_JSON_INCOMPLETE_DATA;
}
CxJsonTokenType ttype = json->uncompleted.tokentype;
size_t token_part_start = json->buffer.pos;
bool escape_end_of_string = ttype ==
CX_JSON_TOKEN_STRING
&& json->uncompleted.content.ptr[json->uncompleted.content.length
-1] ==
'\\';
for (
size_t i = json->buffer.pos; i < json->buffer.size; i++) {
char c = json->buffer.space[i];
if (ttype !=
CX_JSON_TOKEN_STRING) {
CxJsonTokenType ctype = char2ttype(c);
if (ttype ==
CX_JSON_NO_TOKEN) {
if (ctype ==
CX_JSON_TOKEN_SPACE) {
json->buffer.pos++;
continue;
}
else if (ctype ==
CX_JSON_TOKEN_STRING) {
ttype =
CX_JSON_TOKEN_STRING;
token_part_start = i;
}
else if (ctype !=
CX_JSON_NO_TOKEN) {
json->buffer.pos = i +
1;
*result = (CxJsonToken){ctype, false, {
NULL,
0}};
return CX_JSON_NO_ERROR;
}
else {
ttype =
CX_JSON_TOKEN_LITERAL;
token_part_start = i;
}
}
else {
if (ctype !=
CX_JSON_NO_TOKEN) {
*result = token_create(json, false, token_part_start, i);
if (result->tokentype ==
CX_JSON_NO_TOKEN) {
return CX_JSON_BUFFER_ALLOC_FAILED;
}
if (result->tokentype ==
CX_JSON_TOKEN_ERROR) {
return CX_JSON_FORMAT_ERROR_NUMBER;
}
json->buffer.pos = i;
return CX_JSON_NO_ERROR;
}
}
}
else {
if (escape_end_of_string) {
escape_end_of_string = false;
}
else {
if (c ==
'""') {
*result = token_create(json, true, token_part_start, i +
1);
if (result->tokentype ==
CX_JSON_NO_TOKEN) {
return CX_JSON_BUFFER_ALLOC_FAILED;
}
json->buffer.pos = i +
1;
return CX_JSON_NO_ERROR;
}
else if (c ==
'\\') {
escape_end_of_string = true;
}
}
}
}
if (ttype ==
CX_JSON_NO_TOKEN) {
return CX_JSON_NO_DATA;
}
else {
size_t uncompleted_len = json->buffer.size - token_part_start;
if (json->uncompleted.tokentype ==
CX_JSON_NO_TOKEN) {
CxJsonToken uncompleted = {
ttype, true,
cx_strdup(cx_strn(json->buffer.space + token_part_start, uncompleted_len))
};
if (uncompleted.content.ptr ==
NULL) {
return CX_JSON_BUFFER_ALLOC_FAILED;
}
json->uncompleted = uncompleted;
}
else {
assert(json->uncompleted.allocated);
cxmutstr str = cx_strcat_m(json->uncompleted.content,
1,
cx_strn(json->buffer.space + token_part_start, uncompleted_len));
if (str.ptr ==
NULL) {
return CX_JSON_BUFFER_ALLOC_FAILED;
}
json->uncompleted.content = str;
}
json->buffer.pos += uncompleted_len;
return CX_JSON_INCOMPLETE_DATA;
}
}
static unsigned codepoint_to_utf8(
uint32_t codepoint,
char *output_buf) {
if (codepoint <=
0x7F) {
*output_buf = (
char)codepoint;
return 1;
}
else if (codepoint <=
0x7FF) {
output_buf[
0] = (
char)(
0xC0 | ((codepoint >>
6) &
0x1F));
output_buf[
1] = (
char)(
0x80 | (codepoint &
0x3F));
return 2;
}
else if (codepoint <=
0xFFFF) {
output_buf[
0] = (
char)(
0xE0 | ((codepoint >>
12) &
0x0F));
output_buf[
1] = (
char)(
0x80 | ((codepoint >>
6) &
0x3F));
output_buf[
2] = (
char)(
0x80 | (codepoint &
0x3F));
return 3;
}
else if (codepoint <=
0x10FFFF) {
output_buf[
0] = (
char)(
0xF0 | ((codepoint >>
18) &
0x07));
output_buf[
1] = (
char)(
0x80 | ((codepoint >>
12) &
0x3F));
output_buf[
2] = (
char)(
0x80 | ((codepoint >>
6) &
0x3F));
output_buf[
3] = (
char)(
0x80 | (codepoint &
0x3F));
return 4;
}
return 0;
}
static inline
uint32_t utf16pair_to_codepoint(
uint16_t c0,
uint16_t c1) {
return ((c0 -
0xD800) <<
10) + (c1 -
0xDC00) +
0x10000;
}
static unsigned unescape_unicode_string(cxstring str,
char *utf8buf) {
if (str.length <
6 || str.ptr[
0] !=
'\\' || str.ptr[
1] !=
'u') {
return 0;
}
unsigned utf8len =
0;
cxstring ustr1 = { str.ptr +
2,
4};
uint16_t utf16a, utf16b;
if (!cx_strtou16_lc(ustr1, &utf16a,
16,
"")) {
uint32_t codepoint;
if (utf16a <
0xD800 || utf16a >
0xE000) {
codepoint = utf16a;
utf8len = codepoint_to_utf8(codepoint, utf8buf);
}
else if (utf16a >=
0xD800 && utf16a <=
0xDBFF) {
if (str.length >=
12) {
if (str.ptr[
6] ==
'\\' && str.ptr[
7] ==
'u') {
cxstring ustr2 = { str.ptr
+8,
4 };
if (!cx_strtou16_lc(ustr2, &utf16b,
16,
"")
&& utf16b >=
0xDC00 && utf16b <=
0xDFFF) {
codepoint = utf16pair_to_codepoint(utf16a, utf16b);
utf8len = codepoint_to_utf8(codepoint, utf8buf);
}
}
}
}
}
return utf8len;
}
static cxmutstr unescape_string(
const CxAllocator *a, cxmutstr str) {
cxmutstr result;
result.length =
0;
result.ptr = cxMalloc(a, str.length -
1);
if (result.ptr ==
NULL)
return result;
bool u = false;
for (
size_t i =
1; i < str.length -
1; i++) {
char c = str.ptr[i];
if (u) {
u = false;
if (c ==
'n') {
c =
'\n';
}
else if (c ==
'""') {
c =
'""';
}
else if (c ==
't') {
c =
'\t';
}
else if (c ==
'r') {
c =
'\r';
}
else if (c ==
'\\') {
c =
'\\';
}
else if (c ==
'/') {
c =
'/';
}
else if (c ==
'f') {
c =
'\f';
}
else if (c ==
'b') {
c =
'\b';
}
else if (c ==
'u') {
char utf8buf[
4];
unsigned utf8len = unescape_unicode_string(
cx_strn(str.ptr + i -
1, str.length - i),
utf8buf
);
if(utf8len >
0) {
i += utf8len <
4 ?
4 :
10;
utf8len--;
c = utf8buf[utf8len];
for (
unsigned x =
0; x < utf8len; x++) {
result.ptr[result.length++] = utf8buf[x];
}
}
else {
result.ptr[result.length++] =
'\\';
}
}
else {
result.ptr[result.length++] =
'\\';
}
result.ptr[result.length++] = c;
}
else {
if (c ==
'\\') {
u = true;
}
else {
result.ptr[result.length++] = c;
}
}
}
result.ptr[result.length] =
0;
return result;
}
static cxmutstr escape_string(cxstring str, bool escape_slash) {
CxBuffer buf = {
0};
bool all_printable = true;
for (
size_t i =
0; i < str.length; i++) {
unsigned char c = str.ptr[i];
bool escape = c <
0x20 || c ==
'\\' || c ==
'""'
|| (escape_slash && c ==
'/');
if (all_printable && escape) {
size_t capa = str.length +
32;
char *space = cxMallocDefault(capa);
if (space ==
NULL)
return cx_mutstrn(
NULL,
0);
cxBufferInit(&buf, space, capa,
NULL,
CX_BUFFER_AUTO_EXTEND);
cxBufferWrite(str.ptr,
1, i, &buf);
all_printable = false;
}
if (escape) {
cxBufferPut(&buf,
'\\');
if (c ==
'\"') {
cxBufferPut(&buf,
'\"');
}
else if (c ==
'\n') {
cxBufferPut(&buf,
'n');
}
else if (c ==
'\t') {
cxBufferPut(&buf,
't');
}
else if (c ==
'\r') {
cxBufferPut(&buf,
'r');
}
else if (c ==
'\\') {
cxBufferPut(&buf,
'\\');
}
else if (c ==
'/') {
cxBufferPut(&buf,
'/');
}
else if (c ==
'\f') {
cxBufferPut(&buf,
'f');
}
else if (c ==
'\b') {
cxBufferPut(&buf,
'b');
}
else {
char code[
6];
snprintf(code,
sizeof(code),
"u%04x", (
unsigned int) c);
cxBufferPutString(&buf, code);
}
}
else if (!all_printable) {
cxBufferPut(&buf, c);
}
}
cxmutstr ret;
if (all_printable) {
ret = cx_mutstrn((
char*)str.ptr, str.length);
}
else {
ret = cx_mutstrn(buf.space, buf.size);
}
cxBufferDestroy(&buf);
return ret;
}
static CxJsonObject json_create_object_map(
const CxAllocator *allocator) {
CxMap *map = cxKvListCreateAsMap(allocator,
NULL,
CX_STORE_POINTERS);
if (map ==
NULL)
return NULL;
cxDefineDestructor(map, cxJsonValueFree);
return map;
}
static void json_free_object_map(CxJsonObject obj) {
cxMapFree(obj);
}
static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) {
CxJsonValue *v = cxCalloc(json->allocator,
1,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->type = type;
v->allocator = json->allocator;
if (type ==
CX_JSON_ARRAY) {
cx_array_initialize_a(json->allocator, v->array.data,
16);
if (v->array.data ==
NULL)
goto create_json_value_exit_error;
}
else if (type ==
CX_JSON_OBJECT) {
v->object = json_create_object_map(json->allocator);
if (v->object ==
NULL)
goto create_json_value_exit_error;
}
if (json->vbuf_size >
0) {
CxJsonValue *parent = json->vbuf[json->vbuf_size -
1];
assert(parent !=
NULL);
if (parent->type ==
CX_JSON_ARRAY) {
CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator,
NULL);
if (cx_array_simple_add_a(&value_realloc, parent->array.data, v)) {
goto create_json_value_exit_error;
}
}
else if (parent->type ==
CX_JSON_OBJECT) {
assert(json->uncompleted_member_name.ptr !=
NULL);
if (cxMapPut(parent->object, json->uncompleted_member_name, v)) {
goto create_json_value_exit_error;
}
cx_strfree_a(json->allocator, &json->uncompleted_member_name);
}
else {
assert(false);
}
}
if (type ==
CX_JSON_ARRAY || type ==
CX_JSON_OBJECT) {
CxArrayReallocator vbuf_realloc = cx_array_reallocator(
NULL, json->vbuf_internal);
if (cx_array_simple_add_a(&vbuf_realloc, json->vbuf, v)) {
goto create_json_value_exit_error;
}
}
if (json->parsed ==
NULL) {
json->parsed = v;
}
return v;
create_json_value_exit_error:
cxJsonValueFree(v);
return NULL;
}
#define JP_STATE_VALUE_BEGIN 0
#define JP_STATE_VALUE_END 10
#define JP_STATE_VALUE_BEGIN_OBJ 1
#define JP_STATE_OBJ_SEP_OR_CLOSE 11
#define JP_STATE_VALUE_BEGIN_AR 2
#define JP_STATE_ARRAY_SEP_OR_CLOSE 12
#define JP_STATE_OBJ_NAME_OR_CLOSE 5
#define JP_STATE_OBJ_NAME 6
#define JP_STATE_OBJ_COLON 7
void cxJsonInit(CxJson *json,
const CxAllocator *allocator) {
if (allocator ==
NULL) {
allocator = cxDefaultAllocator;
}
memset(json,
0,
sizeof(CxJson));
json->allocator = allocator;
json->states = json->states_internal;
json->states_capacity = cx_nmemb(json->states_internal);
json->states[
0] =
JP_STATE_VALUE_BEGIN;
json->states_size =
1;
json->vbuf = json->vbuf_internal;
json->vbuf_capacity = cx_nmemb(json->vbuf_internal);
}
void cxJsonDestroy(CxJson *json) {
cxBufferDestroy(&json->buffer);
if (json->states != json->states_internal) {
cxFreeDefault(json->states);
}
if (json->vbuf != json->vbuf_internal) {
cxFreeDefault(json->vbuf);
}
cxJsonValueFree(json->parsed);
json->parsed =
NULL;
token_destroy(&json->uncompleted);
cx_strfree_a(json->allocator, &json->uncompleted_member_name);
}
void cxJsonReset(CxJson *json) {
const CxAllocator *allocator = json->allocator;
cxJsonDestroy(json);
cxJsonInit(json, allocator);
}
int cxJsonFilln(CxJson *json,
const char *buf,
size_t size) {
if (cxBufferEof(&json->buffer)) {
cxBufferDestroy(&json->buffer);
if (buf ==
NULL) buf =
"";
cxBufferInit(&json->buffer, (
char*) buf, size,
NULL,
CX_BUFFER_AUTO_EXTEND |
CX_BUFFER_COPY_ON_WRITE);
json->buffer.size = size;
return 0;
}
else {
return size != cxBufferAppend(buf,
1, size, &json->buffer);
}
}
static void json_add_state(CxJson *json,
int state) {
json->states[json->states_size++] = state;
}
#define return_rec(code) \
token_destroy(&token); \
return code
static enum cx_json_status json_parse(CxJson *json) {
CxJsonValue *vbuf =
NULL;
CxJsonToken token;
{
enum cx_json_status ret = token_parse_next(json, &token);
if (ret !=
CX_JSON_NO_ERROR) {
return ret;
}
}
assert(json->states_size >
0);
int state = json->states[--json->states_size];
CxArrayReallocator state_realloc = cx_array_reallocator(
NULL, json->states_internal);
if (cx_array_simple_reserve_a(&state_realloc, json->states,
2)) {
return CX_JSON_BUFFER_ALLOC_FAILED;
}
if (state <
3) {
json_add_state(json,
10 + state);
switch (token.tokentype) {
case CX_JSON_TOKEN_BEGIN_ARRAY: {
if (json_create_value(json,
CX_JSON_ARRAY) ==
NULL) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
json_add_state(json,
JP_STATE_VALUE_BEGIN_AR);
return_rec(
CX_JSON_NO_ERROR);
}
case CX_JSON_TOKEN_BEGIN_OBJECT: {
if (json_create_value(json,
CX_JSON_OBJECT) ==
NULL) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
json_add_state(json,
JP_STATE_OBJ_NAME_OR_CLOSE);
return_rec(
CX_JSON_NO_ERROR);
}
case CX_JSON_TOKEN_STRING: {
if ((vbuf = json_create_value(json,
CX_JSON_STRING)) ==
NULL) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
cxmutstr str = unescape_string(json->allocator, token.content);
if (str.ptr ==
NULL) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
vbuf->string = str;
return_rec(
CX_JSON_NO_ERROR);
}
case CX_JSON_TOKEN_INTEGER:
case CX_JSON_TOKEN_NUMBER: {
int type = token.tokentype ==
CX_JSON_TOKEN_INTEGER ?
CX_JSON_INTEGER :
CX_JSON_NUMBER;
if (
NULL == (vbuf = json_create_value(json, type))) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
if (type ==
CX_JSON_INTEGER) {
if (cx_strtoi64(token.content, &vbuf->integer,
10)) {
return_rec(
CX_JSON_FORMAT_ERROR_NUMBER);
}
}
else {
if (cx_strtod(token.content, &vbuf->number)) {
return_rec(
CX_JSON_FORMAT_ERROR_NUMBER);
}
}
return_rec(
CX_JSON_NO_ERROR);
}
case CX_JSON_TOKEN_LITERAL: {
if ((vbuf = json_create_value(json,
CX_JSON_LITERAL)) ==
NULL) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
if (
0 == cx_strcmp(cx_strcast(token.content), cx_str(
"true"))) {
vbuf->literal =
CX_JSON_TRUE;
}
else if (
0 == cx_strcmp(cx_strcast(token.content), cx_str(
"false"))) {
vbuf->literal =
CX_JSON_FALSE;
}
else {
vbuf->literal =
CX_JSON_NULL;
}
return_rec(
CX_JSON_NO_ERROR);
}
default: {
return_rec(
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
}
}
}
else if (state ==
JP_STATE_ARRAY_SEP_OR_CLOSE) {
if (token.tokentype ==
CX_JSON_TOKEN_VALUE_SEPARATOR) {
json_add_state(json,
JP_STATE_VALUE_BEGIN_AR);
return_rec(
CX_JSON_NO_ERROR);
}
else if (token.tokentype ==
CX_JSON_TOKEN_END_ARRAY) {
json->vbuf_size--;
return_rec(
CX_JSON_NO_ERROR);
}
else {
return_rec(
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
}
}
else if (state ==
JP_STATE_OBJ_NAME_OR_CLOSE || state ==
JP_STATE_OBJ_NAME) {
if (state ==
JP_STATE_OBJ_NAME_OR_CLOSE && token.tokentype ==
CX_JSON_TOKEN_END_OBJECT) {
json->vbuf_size--;
return_rec(
CX_JSON_NO_ERROR);
}
else {
if (token.tokentype !=
CX_JSON_TOKEN_STRING) {
return_rec(
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
}
cxmutstr name = unescape_string(json->allocator, token.content);
if (name.ptr ==
NULL) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
assert(json->uncompleted_member_name.ptr ==
NULL);
json->uncompleted_member_name = name;
assert(json->vbuf_size >
0);
json_add_state(json,
JP_STATE_OBJ_COLON);
return_rec(
CX_JSON_NO_ERROR);
}
}
else if (state ==
JP_STATE_OBJ_COLON) {
if (token.tokentype !=
CX_JSON_TOKEN_NAME_SEPARATOR) {
return_rec(
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
}
json_add_state(json,
JP_STATE_VALUE_BEGIN_OBJ);
return_rec(
CX_JSON_NO_ERROR);
}
else if (state ==
JP_STATE_OBJ_SEP_OR_CLOSE) {
if (token.tokentype ==
CX_JSON_TOKEN_VALUE_SEPARATOR) {
json_add_state(json,
JP_STATE_OBJ_NAME);
return_rec(
CX_JSON_NO_ERROR);
}
else if (token.tokentype ==
CX_JSON_TOKEN_END_OBJECT) {
json->vbuf_size--;
return_rec(
CX_JSON_NO_ERROR);
}
else {
return_rec(
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
}
}
else {
assert(false);
return_rec(
-1);
}
}
CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) {
*value = &cx_json_value_nothing;
if (json->buffer.space ==
NULL) {
return CX_JSON_NULL_DATA;
}
CxJsonStatus result;
do {
result = json_parse(json);
if (result ==
CX_JSON_NO_ERROR && json->states_size ==
1) {
assert(json->states[
0] ==
JP_STATE_VALUE_END);
assert(json->vbuf_size ==
0);
*value = json->parsed;
json->parsed =
NULL;
json->states[
0] =
JP_STATE_VALUE_BEGIN;
return CX_JSON_NO_ERROR;
}
}
while (result ==
CX_JSON_NO_ERROR);
if (result ==
CX_JSON_NO_DATA && json->states_size >
1) {
return CX_JSON_INCOMPLETE_DATA;
}
return result;
}
CxJsonStatus cx_json_from_string(
const CxAllocator *allocator,
cxstring str, CxJsonValue **value) {
*value = &cx_json_value_nothing;
CxJson parser;
cxJsonInit(&parser, allocator);
if (cxJsonFill(&parser, str)) {
cxJsonDestroy(&parser);
return CX_JSON_BUFFER_ALLOC_FAILED;
}
CxJsonStatus status = cxJsonNext(&parser, value);
CxJsonValue *chk_value =
NULL;
CxJsonStatus chk_status =
CX_JSON_NO_DATA;
if (status ==
CX_JSON_NO_ERROR) {
chk_status = cxJsonNext(&parser, &chk_value);
}
cxJsonDestroy(&parser);
if (chk_status ==
CX_JSON_NO_DATA) {
return status;
}
else {
cxJsonValueFree(*value);
cxJsonValueFree(chk_value);
*value = &cx_json_value_nothing;
return CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN;
}
}
void cxJsonValueFree(CxJsonValue *value) {
if (value ==
NULL || value->type ==
CX_JSON_NOTHING)
return;
switch (value->type) {
case CX_JSON_OBJECT: {
json_free_object_map(value->object);
break;
}
case CX_JSON_ARRAY: {
CxJsonArray array = value->array;
for (
size_t i =
0; i < array.data_size; i++) {
cxJsonValueFree(array.data[i]);
}
cxFree(value->allocator, array.data);
break;
}
case CX_JSON_STRING: {
cxFree(value->allocator, value->string.ptr);
break;
}
default: {
break;
}
}
cxFree(value->allocator, value);
}
CxJsonValue* cxJsonCreateObj(
const CxAllocator* allocator) {
if (allocator ==
NULL) allocator = cxDefaultAllocator;
CxJsonValue* v = cxMalloc(allocator,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->allocator = allocator;
v->type =
CX_JSON_OBJECT;
v->object = json_create_object_map(allocator);
if (v->object ==
NULL) {
cxFree(allocator, v);
return NULL;
}
return v;
}
CxJsonValue* cxJsonCreateArr(
const CxAllocator* allocator) {
if (allocator ==
NULL) allocator = cxDefaultAllocator;
CxJsonValue* v = cxMalloc(allocator,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->allocator = allocator;
v->type =
CX_JSON_ARRAY;
cx_array_initialize_a(allocator, v->array.data,
16);
if (v->array.data ==
NULL) { cxFree(allocator, v);
return NULL; }
return v;
}
CxJsonValue* cxJsonCreateNumber(
const CxAllocator* allocator,
double num) {
if (allocator ==
NULL) allocator = cxDefaultAllocator;
CxJsonValue* v = cxMalloc(allocator,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->allocator = allocator;
v->type =
CX_JSON_NUMBER;
v->number = num;
return v;
}
CxJsonValue* cxJsonCreateInteger(
const CxAllocator* allocator,
int64_t num) {
if (allocator ==
NULL) allocator = cxDefaultAllocator;
CxJsonValue* v = cxMalloc(allocator,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->allocator = allocator;
v->type =
CX_JSON_INTEGER;
v->integer = num;
return v;
}
CxJsonValue* cx_json_create_string(
const CxAllocator* allocator, cxstring str) {
if (allocator ==
NULL) allocator = cxDefaultAllocator;
CxJsonValue* v = cxMalloc(allocator,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->allocator = allocator;
v->type =
CX_JSON_STRING;
cxmutstr s = cx_strdup_a(allocator, str);
if (s.ptr ==
NULL) { cxFree(allocator, v);
return NULL; }
v->string = s;
return v;
}
CxJsonValue* cxJsonCreateLiteral(
const CxAllocator* allocator, CxJsonLiteral lit) {
if (allocator ==
NULL) allocator = cxDefaultAllocator;
CxJsonValue* v = cxMalloc(allocator,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->allocator = allocator;
v->type =
CX_JSON_LITERAL;
v->literal = lit;
return v;
}
static void json_arr_free_temp(CxJsonValue** values,
size_t count) {
for (
size_t i =
0; i < count; i++) {
if (values[i] ==
NULL)
break;
cxJsonValueFree(values[i]);
}
cxFreeDefault(values);
}
int cxJsonArrAddNumbers(CxJsonValue* arr,
const double* num,
size_t count) {
CxJsonValue** values = cxCallocDefault(count,
sizeof(CxJsonValue*));
if (values ==
NULL)
return -1;
for (
size_t i =
0; i < count; i++) {
values[i] = cxJsonCreateNumber(arr->allocator, num[i]);
if (values[i] ==
NULL) { json_arr_free_temp(values, count);
return -1; }
}
int ret = cxJsonArrAddValues(arr, values, count);
cxFreeDefault(values);
return ret;
}
int cxJsonArrAddIntegers(CxJsonValue* arr,
const int64_t* num,
size_t count) {
CxJsonValue** values = cxCallocDefault(count,
sizeof(CxJsonValue*));
if (values ==
NULL)
return -1;
for (
size_t i =
0; i < count; i++) {
values[i] = cxJsonCreateInteger(arr->allocator, num[i]);
if (values[i] ==
NULL) { json_arr_free_temp(values, count);
return -1; }
}
int ret = cxJsonArrAddValues(arr, values, count);
cxFreeDefault(values);
return ret;
}
int cxJsonArrAddStrings(CxJsonValue* arr,
const char*
const* str,
size_t count) {
CxJsonValue** values = cxCallocDefault(count,
sizeof(CxJsonValue*));
if (values ==
NULL)
return -1;
for (
size_t i =
0; i < count; i++) {
values[i] = cxJsonCreateString(arr->allocator, str[i]);
if (values[i] ==
NULL) { json_arr_free_temp(values, count);
return -1; }
}
int ret = cxJsonArrAddValues(arr, values, count);
cxFreeDefault(values);
return ret;
}
int cxJsonArrAddCxStrings(CxJsonValue* arr,
const cxstring* str,
size_t count) {
CxJsonValue** values = cxCallocDefault(count,
sizeof(CxJsonValue*));
if (values ==
NULL)
return -1;
for (
size_t i =
0; i < count; i++) {
values[i] = cxJsonCreateString(arr->allocator, str[i]);
if (values[i] ==
NULL) { json_arr_free_temp(values, count);
return -1; }
}
int ret = cxJsonArrAddValues(arr, values, count);
cxFreeDefault(values);
return ret;
}
int cxJsonArrAddLiterals(CxJsonValue* arr,
const CxJsonLiteral* lit,
size_t count) {
CxJsonValue** values = cxCallocDefault(count,
sizeof(CxJsonValue*));
if (values ==
NULL)
return -1;
for (
size_t i =
0; i < count; i++) {
values[i] = cxJsonCreateLiteral(arr->allocator, lit[i]);
if (values[i] ==
NULL) { json_arr_free_temp(values, count);
return -1; }
}
int ret = cxJsonArrAddValues(arr, values, count);
cxFreeDefault(values);
return ret;
}
int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue*
const* val,
size_t count) {
CxArrayReallocator value_realloc = cx_array_reallocator(arr->allocator,
NULL);
assert(arr->type ==
CX_JSON_ARRAY);
return cx_array_simple_copy_a(&value_realloc,
arr->array.data,
arr->array.data_size,
val, count
);
}
int cx_json_obj_put(CxJsonValue* obj, cxstring name, CxJsonValue* child) {
return cxMapPut(obj->object, name, child);
}
CxJsonValue* cx_json_obj_put_obj(CxJsonValue* obj, cxstring name) {
CxJsonValue* v = cxJsonCreateObj(obj->allocator);
if (v ==
NULL)
return NULL;
if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v);
return NULL; }
return v;
}
CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name) {
CxJsonValue* v = cxJsonCreateArr(obj->allocator);
if (v ==
NULL)
return NULL;
if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v);
return NULL; }
return v;
}
CxJsonValue* cx_json_obj_put_number(CxJsonValue* obj, cxstring name,
double num) {
CxJsonValue* v = cxJsonCreateNumber(obj->allocator, num);
if (v ==
NULL)
return NULL;
if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v);
return NULL; }
return v;
}
CxJsonValue* cx_json_obj_put_integer(CxJsonValue* obj, cxstring name,
int64_t num) {
CxJsonValue* v = cxJsonCreateInteger(obj->allocator, num);
if (v ==
NULL)
return NULL;
if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v);
return NULL; }
return v;
}
CxJsonValue* cx_json_obj_put_string(CxJsonValue* obj, cxstring name, cxstring str) {
CxJsonValue* v = cxJsonCreateString(obj->allocator, str);
if (v ==
NULL)
return NULL;
if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v);
return NULL; }
return v;
}
CxJsonValue* cx_json_obj_put_literal(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) {
CxJsonValue* v = cxJsonCreateLiteral(obj->allocator, lit);
if (v ==
NULL)
return NULL;
if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v);
return NULL;}
return v;
}
CxJsonValue *cxJsonArrGet(
const CxJsonValue *value,
size_t index) {
if (index >= value->array.data_size) {
return &cx_json_value_nothing;
}
return value->array.data[index];
}
CxJsonValue *cxJsonArrRemove(CxJsonValue *value,
size_t index) {
if (index >= value->array.data_size) {
return NULL;
}
CxJsonValue *ret = value->array.data[index];
size_t count = value->array.data_size - index -
1;
if (count >
0) {
memmove(value->array.data + index, value->array.data + index +
1, count *
sizeof(CxJsonValue*));
}
value->array.data_size--;
return ret;
}
char *cxJsonAsString(
const CxJsonValue *value) {
return value->string.ptr;
}
cxstring cxJsonAsCxString(
const CxJsonValue *value) {
return cx_strcast(value->string);
}
cxmutstr cxJsonAsCxMutStr(
const CxJsonValue *value) {
return value->string;
}
double cxJsonAsDouble(
const CxJsonValue *value) {
if (value->type ==
CX_JSON_INTEGER) {
return (
double) value->integer;
}
else {
return value->number;
}
}
int64_t cxJsonAsInteger(
const CxJsonValue *value) {
if (value->type ==
CX_JSON_INTEGER) {
return value->integer;
}
else {
return (
int64_t) value->number;
}
}
CxIterator cxJsonArrIter(
const CxJsonValue *value) {
return cxIteratorPtr(
value->array.data,
value->array.data_size,
true
);
}
CxMapIterator cxJsonObjIter(
const CxJsonValue *value) {
return cxMapIterator(value->object);
}
CxJsonValue *cx_json_obj_get(
const CxJsonValue *value, cxstring name) {
CxJsonValue *v = cxMapGet(value->object, name);
if (v ==
NULL) {
return &cx_json_value_nothing;
}
else {
return v;
}
}
CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name) {
CxJsonValue *v =
NULL;
cxMapRemoveAndGet(value->object, name, &v);
return v;
}
CxJsonWriter cxJsonWriterCompact(
void) {
return (CxJsonWriter) {
false,
6,
false,
4,
false
};
}
CxJsonWriter cxJsonWriterPretty(bool use_spaces) {
return (CxJsonWriter) {
true,
6,
use_spaces,
4,
false
};
}
static int cx_json_writer_indent(
void *target,
cx_write_func wfunc,
const CxJsonWriter *settings,
unsigned int depth
) {
if (depth ==
0)
return 0;
const char* indent;
size_t width = depth;
if (settings->indent_space) {
if (settings->indent ==
0)
return 0;
width *= settings->indent;
indent =
" ";
}
else {
indent =
"\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
}
size_t full = width /
32;
size_t remaining = width %
32;
for (
size_t i =
0; i < full; i++) {
if (
32 != wfunc(indent,
1,
32, target))
return 1;
}
if (remaining != wfunc(indent,
1, remaining, target))
return 1;
return 0;
}
int cx_json_write_rec(
void *target,
const CxJsonValue *value,
cx_write_func wfunc,
const CxJsonWriter *settings,
unsigned int depth
) {
size_t actual =
0, expected =
0;
char numbuf[
40];
switch (value->type) {
case CX_JSON_OBJECT: {
const char *begin_obj =
"{\n";
if (settings->pretty) {
actual += wfunc(begin_obj,
1,
2, target);
expected +=
2;
}
else {
actual += wfunc(begin_obj,
1,
1, target);
expected++;
}
depth++;
CxMapIterator member_iter = cxJsonObjIter(value);
cx_foreach(
const CxMapEntry *, member, member_iter) {
if (settings->pretty) {
if (cx_json_writer_indent(target, wfunc, settings, depth)) {
return 1;
}
}
actual += wfunc(
"\"",
1,
1, target);
cxstring key = cx_strn(member->key->data, member->key->len);
cxmutstr name = escape_string(key, settings->escape_slash);
actual += wfunc(name.ptr,
1, name.length, target);
actual += wfunc(
"\"",
1,
1, target);
const char *obj_name_sep =
": ";
if (settings->pretty) {
actual += wfunc(obj_name_sep,
1,
2, target);
expected +=
4 + name.length;
}
else {
actual += wfunc(obj_name_sep,
1,
1, target);
expected +=
3 + name.length;
}
if (name.ptr != key.ptr) {
cx_strfree(&name);
}
if (cx_json_write_rec(target, member->value, wfunc, settings, depth))
return 1;
if (member_iter.index < member_iter.elem_count -
1) {
const char *obj_value_sep =
",\n";
if (settings->pretty) {
actual += wfunc(obj_value_sep,
1,
2, target);
expected +=
2;
}
else {
actual += wfunc(obj_value_sep,
1,
1, target);
expected++;
}
}
else {
if (settings->pretty) {
actual += wfunc(
"\n",
1,
1, target);
expected ++;
}
}
}
depth--;
if (settings->pretty) {
if (cx_json_writer_indent(target, wfunc, settings, depth))
return 1;
}
actual += wfunc(
"}",
1,
1, target);
expected++;
break;
}
case CX_JSON_ARRAY: {
actual += wfunc(
"[",
1,
1, target);
expected++;
CxIterator iter = cxJsonArrIter(value);
cx_foreach(CxJsonValue*, element, iter) {
if (cx_json_write_rec(
target, element,
wfunc, settings, depth)
) {
return 1;
}
if (iter.index < iter.elem_count -
1) {
const char *arr_value_sep =
", ";
if (settings->pretty) {
actual += wfunc(arr_value_sep,
1,
2, target);
expected +=
2;
}
else {
actual += wfunc(arr_value_sep,
1,
1, target);
expected++;
}
}
}
actual += wfunc(
"]",
1,
1, target);
expected++;
break;
}
case CX_JSON_STRING: {
actual += wfunc(
"\"",
1,
1, target);
cxmutstr str = escape_string(cx_strcast(value->string),
settings->escape_slash);
actual += wfunc(str.ptr,
1, str.length, target);
actual += wfunc(
"\"",
1,
1, target);
expected +=
2 + str.length;
if (str.ptr != value->string.ptr) {
cx_strfree(&str);
}
break;
}
case CX_JSON_NUMBER: {
int precision = settings->frac_max_digits;
precision =
1 + (precision >
15 ?
30 :
2 * precision);
snprintf(numbuf,
40,
"%.*g", precision, value->number);
char *dot, *exp;
unsigned char max_digits;
dot = strchr(numbuf,
'.');
if (dot ==
NULL) {
dot = strchr(numbuf,
',');
}
if (dot ==
NULL) {
max_digits =
30;
dot = numbuf;
}
else {
size_t len = dot - numbuf;
actual += wfunc(numbuf,
1, len, target);
expected += len;
max_digits = settings->frac_max_digits;
if (max_digits >
15) {
max_digits =
15;
}
if (max_digits >
0) {
actual += wfunc(
".",
1,
1, target);
expected++;
}
dot++;
}
exp = strchr(dot,
'e');
if (exp ==
NULL) {
if (max_digits >
0) {
size_t len = strlen(dot);
if (len > max_digits) {
len = max_digits;
}
actual += wfunc(dot,
1, len, target);
expected += len;
}
}
else {
if (max_digits >
0) {
size_t len = exp - dot -
1;
if (len > max_digits) {
len = max_digits;
}
actual += wfunc(dot,
1, len, target);
expected += len;
}
actual += wfunc(
"e",
1,
1, target);
expected++;
exp++;
size_t len = strlen(exp);
actual += wfunc(exp,
1, len, target);
expected += len;
}
break;
}
case CX_JSON_INTEGER: {
snprintf(numbuf,
32,
"%" PRIi64, value->integer);
size_t len = strlen(numbuf);
actual += wfunc(numbuf,
1, len, target);
expected += len;
break;
}
case CX_JSON_LITERAL: {
if (value->literal ==
CX_JSON_TRUE) {
actual += wfunc(
"true",
1,
4, target);
expected +=
4;
}
else if (value->literal ==
CX_JSON_FALSE) {
actual += wfunc(
"false",
1,
5, target);
expected +=
5;
}
else {
actual += wfunc(
"null",
1,
4, target);
expected +=
4;
}
break;
}
case CX_JSON_NOTHING: {
break;
}
default: assert(false);
}
return expected != actual;
}
int cxJsonWrite(
void *target,
const CxJsonValue *value,
cx_write_func wfunc,
const CxJsonWriter *settings
) {
assert(target !=
NULL);
assert(value !=
NULL);
assert(wfunc !=
NULL);
CxJsonWriter writer_default = cxJsonWriterCompact();
if (settings ==
NULL) {
settings = &writer_default;
}
return cx_json_write_rec(target, value, wfunc, settings,
0);
}