Home Rumble Youtube Twitter/X Kofi Contact / Crypto

c_json

A pure-C JSON parser and writer library with no dependencies. It can handle much more relaxed JSON formating, even more relaxed than literals used in Javascript:
  • Keys do not need to be quoted, according to the same rules as Javascript literals.
  • String values with no internal whitespace do not need to be quoted. Perfect for enums.
  • JS-style comments are allowed, both single-line and douple-line.
  • Keys and values can use either single or double quotes.
  • Trailing commas are allowed.
  • Multi-line strings are allowed and include all newlines and whitespace inside them.
  • Supports C-style octal, hex, and binary integers.
  • Numbers without decimals are parsed and stored as signed 64-bit integers, preserving the full number range.

Compiling

Repos:
Github
Notabug (Currently down due to the Goddamn AI scrapers. Fuck Sam Altman.)
Gitlab
Codeberg

Symlink or copy the code into your project however you wish, #include "c_json/json.h", and add c_json/json.c to your build files.

Quick Usage

Reading from a JSON file

json_file_t* jsf = json_load_path(path); if(!jsf || !jsf->root) { if(jsf) json_file_free(jsf); return; } json_value_t* panes_j = json_obj_get_val(jsf->root, "panes"); void* piter = NULL; char* panename; json_value_t* pane_j; while(json_obj_next(panes_j, &piter, &panename, &pane_j)) { DebugPane* dp = DebugPanel_GetPane(dpl, panename); if(!dp) continue; if(dp->type == DEBUGPANE_Vars) {; json_value_t* cats_j = json_obj_get_val(pane_j, "byFn"); void* iter = NULL; char* catname; json_value_t* cat_j; while(json_obj_next(cats_j, &iter, &catname, &cat_j)) { if(dp->type == DEBUGPANE_Vars) { int collapsed = json_obj_get_int(cat_j, "collapsed", 0); char* name = strdup(catname); HT_set(&dp->Vars.catCollapseState, name, collapsed); } } } if(json_obj_get_int(pane_j, "current", 0)) { VEC_EACH(&dpl->panes, i, pane) { if(0 == strcmp(pane->name, panename)) { dpl->currentPane = i; break; } } } } json_file_free(jsf);

Writing to a JSON file

json_value_t* root_j = json_new_object(8); json_value_t* panes_j = json_new_object(8); json_obj_set_key(root_j, "panes", panes_j); VEC_EACH(&dpl->panes, i, dp) { json_value_t* pane_j = json_new_object(32); json_obj_set_key(panes_j, dp->name, pane_j); if(dp->type == DEBUGPANE_Vars) { json_value_t* cats_j = json_new_object(32); json_obj_set_key(pane_j, "byFn", cats_j); HT_EACH(&dp->Vars.byFn, name, DebugPanelCategory*, cat) { json_value_t* cat_j = json_new_object(8); json_value_t* v = json_new_int(cat->collapsed); json_obj_set_key(cat_j, "collapsed", v); json_obj_set_key(cats_j, name, cat_j); } } if(dpl->currentPane == i) { json_value_t* v = json_new_int(1); json_obj_set_key(pane_j, "current", v); } } struct json_write_context ctx = {0}; ctx.sb = json_string_buffer_create(4096); json_stringify(&ctx, root_j); // write file overwriteFileSync(path, ctx.sb->buf, ctx.sb->length); json_string_buffer_free(ctx.sb); json_free(root_j);

Error Values

JSON_ERROR_NONE = 0No error. Everything is fine.
JSON_ERROR_OOM
JSON_LEX_ERROR_NULL_IN_STRINGA null byte was found in a string.
JSON_LEX_ERROR_NULL_BYTEA null byte was found in the file.
JSON_LEX_ERROR_INVALID_STRINGA string's syntax is invalid.
JSON_LEX_ERROR_UNEXPECTED_END_OF_INPUTThe file or string ended in the middle of a token, like a string that is never closed.
JSON_LEX_ERROR_INVALID_CHAR
JSON_PARSER_ERROR_CORRUPT_STACK
JSON_PARSER_ERROR_STACK_EXHAUSTED
JSON_PARSER_ERROR_UNEXPECTED_EOIThe file or string ended before completing everything that was started in the file.
JSON_PARSER_ERROR_UNEXPECTED_TOKENThe file's syntax is invalid.
JSON_PARSER_ERROR_BRACE_MISMATCHObject braces ({ }) are mismatched.
JSON_PARSER_ERROR_BRACKET_MISMATCHArray brackets ([ ]) are mismatched.
JSON_ERROR_MAXVALUEOne greater than the largest error enum.

JSON Values

typedef struct json_value { enum json_type type; int base; size_t len; union { int64_t n; uint64_t u; double d; char* s; struct { /* private */ } obj; struct { struct json_link* head, *tail; } arr; }; } json_value_t;

Type Enums

JSON_TYPE_UNDEFINED = 0JSON undefined literal, or an uninitialized node.
JSON_TYPE_NULLJSON NULL literal.
JSON_TYPE_INTSigned integer, stored as a signed 64-bit integer.
JSON_TYPE_DOUBLEFloating point value, stored in double precision.
JSON_TYPE_STRINGString. Can be delimited by single or double quotes, or even no quotes if it would be a valid C identifier and isn't some other JSON value like NULL.
JSON_TYPE_BOOLtrue or false literals
JSON_TYPE_OBJJSON object ({ })
JSON_TYPE_ARRAYJSON array ([ ])
JSON_TYPE_COMMENT_SINGLESingle ling comment (// ) contents
JSON_TYPE_COMMENT_MULTIMultiline (/* */) comment contents
JSON_TYPE_MAXVALUENot a real node type. Used as the loop limit to iterate over the array of type names.

Type Coercion

int json_as_type(json_value_t* v, enum json_type t, void* out) int64_t json_as_int(json_value_t* v) double json_as_double(json_value_t* v) char* json_as_strdup(json_value_t* v) float json_as_float(json_value_t* v)
Coerces a value according to the following rules:
  • undefined/null -> int = 0
  • numbers, as you would expect, according to C type conversion rules
  • obj/array -> number/string = error
  • string/comment -> string = string
  • number -> string = sprintf, accoding to some nice rules.
  • string -> number = strtod/i

Arrays

Arrays are stored as linked lists internally. It's not the most efficient structure, but it's trivial to modify the order and traverse.

typedef struct json_link { struct json_link* next, *prev; struct json_value* v; } json_link_t;

int json_array_push_head(json_value_t* arr, json_value_t* val)
O(1)
Adds val to the beginning of the array.
int json_array_push_tail(json_value_t* arr, json_value_t* val)
O(1)
Adds val to the end of the array.
json_value_t* json_array_pop_head(json_value_t* arr)
O(1)
Removes the first link from the array and returns its value.
json_value_t* json_array_pop_tail(json_value_t* arr)
O(1)
Removes the last link from the array and returns its value.
size_t json_array_calc_length(json_value_t* arr)
O(n)
Walks the linked list and counts the number of elements. Normally unnecessary since the above functions automatically track the array length. Used when you have manually edited the array and want to recalculate the length from scratch.

Objects

Objects are stored in a hash table internally.
int json_obj_get_key(json_value_t* obj, char* key, json_value_t** val)
O(1)
Looks up the key and returns 0 if *val is set to the value. If the key was not found, *val is set to NULL and 1 is returned. c_json does not consider a missing key to have the value undefined.
char* json_obj_key_as_string(json_value_t* obj, char* key)
O(1)
Coerces and strdup's the value at key, or returns NULL if the key does not exist.
char* json_obj_get_strdup(json_value_t* obj, char* key)
O(1) for the lookup, O(n) for the strdup
Returns a newly allocated copy of the value if it is a string, or NULL if it's not a string.
char* json_obj_get_str(json_value_t* obj, char* key)
O(1)
Returns a pointer to the internal string, or NULL if it's not a string.
double json_obj_get_double(json_value_t* obj, char* key, double def)
O(1)
Returns a double if the value at key can be reasonbly coerced to a double, or def if it cannot or does not exist.
int64_t json_obj_get_int(json_value_t* obj, char* key, int64_t def)
O(1)
Returns an int64 if the value at key can be reasonbly coerced to an integer, or def if it cannot or does not exist.
json_value_t* json_obj_get_val(json_value_t* obj, char* key)
O(1)
Returns the value at key, or NULL if it does not exist.
int json_obj_next(json_value_t* val, void** iter, char** key, json_value_t** value)
O(1)
Facilitates manual iteration over an object. Set iter to NULL to start. key and value are then set to the next pair with each call. Returns 1 when there are more elements left, 0 when iteration is complete. Behavior is undefined if the object is modified during iteration. The contents are traversed in no specific order.
There is a convenience macro that wraps this function and works like a normal loop in C. The break and continue keywords work like you expect. JSON_OBJ_EACH(obj, key, val) { if(val->type == JSON_TYPE_STRING) { printf("%s = %s\n", key, val->s); } }
int json_obj_set_key(json_value_t* obj, char* key, json_value_t* val)
O(1)
Inserts val at key into the object. Key is copied internally. Returns 0 on success.
int json_obj_set_key_nodup(json_value_t* obj, char* key, json_value_t* val)
Inserts val at key and takes posession of key's memory.
json_value_t* json_deep_copy(json_value_t* v)
Returns a copy of the entire structure inside v, completely independent of v.
void json_merge(json_value_t* into, json_value_t* from)
Merges a copy of the contents of from into into, recursing into objects and arrays.

File Loading and String Parsing

typedef struct json_file { json_value_t* root; void* lex_info; // private enum json_error error; char* error_str; long error_line_num; long error_char_num; } json_file_t; json_file_t* json_load_path(char* path); json_file_t* json_read_file(FILE* f); json_file_t* json_parse_string(char* source, size_t len); // recursive. void json_free(json_value_t* v); void json_file_free(json_file_t* jsf);

File Saving and String Formatting

typedef struct json_output_format { char indentChar; char indentAmt; char trailingComma; // char commaBeforeElem; char objColonSpace; char noQuoteKeys; char useSingleQuotes; // char escapeNonLatin; // char breakLongStrings; int minArraySzExpand; int minObjSzExpand; int maxLineLength; // only wraps after the comma on array/obj elements char* floatFormat; } json_output_format_t; typedef struct json_write_context { int depth; json_string_buffer_t* sb; json_output_format_t fmt; } json_write_context_t; json_value_t* json_new_str(char* s); json_value_t* json_new_strn(char* s, size_t len); json_value_t* json_new_double(double d); json_value_t* json_new_int(int64_t n); json_value_t* json_new_array(void); json_value_t* json_new_object(size_t initial_alloc_size); json_value_t* json_new_null(void); json_value_t* json_new_undefined(void); json_value_t* json_new_true(void); json_value_t* json_new_false(void); json_string_buffer_t* json_string_buffer_create(size_t initSize); void json_string_buffer_free(json_string_buffer_t* sb); void json_stringify(json_write_context_t* ctx, json_value_t* v);