3
\$\begingroup\$

About

I've decided to challenge myself and write a JSON parser in C99 (i've written them in C++ before, but never in plain old C), and here's what I ended up with, it works well from what I've tried.

The code was tested with a few relatively complex json files, and it had no problem handling them, I also ran it through clang-check -analyze, and it had a few minor warnings that I fixed.

Everything (except for the hash algorithm) was written by me.

Info

  • The underlying type, JSON_Element, is a tagged union;
  • JSON_Object is a hash table;
  • JSON_Array is a dynamic array a-la std::vector;
  • JSON_String is a fixed-size null terminated C-string;
  • JSON_Integer is simply a int64_t;
  • JSON_Double is simply a double;
  • true, false and null are represented as union tags without associated structs;

Code

json.h

#pragma once

#include "utils.h"

#ifdef __cplusplus
#include <cstdio>
extern "C" {
#else
#include <stdio.h>
#endif // __cplusplus

typedef struct JSON_Element JSON_Element;

typedef enum JSON_Element_Type {
    JSON_NULL,
    JSON_ARRAY,
    JSON_OBJECT,
    JSON_INTEGER,
    JSON_DOUBLE,
    JSON_STRING,
    JSON_TRUE,
    JSON_FALSE,
    JSON_PARSE_ERROR,//JSON_PARSE_ERROR is a JSON_STRING that describes the error that happened during json_parse
} JSON_Element_Type;

typedef struct JSON_Object_Table JSON_Object_Table;

typedef struct JSON_Object {
    JSON_Element_Type type;
    JSON_Object_Table * tbl;
} JSON_Object;

JSON_Object * json_make_object();

JSON_Element * json_object_get(JSON_Object *,const char * key);//pointers returned from this are 'fragile' they may break when modifying the object
JSON_Element * json_object_get_n(JSON_Object *,const char * key,size_t n);//pointers returned from this are 'fragile' they may break when modifying the object

void json_object_set(JSON_Object *,const char * key,JSON_Element * elem);//elem pointer is invalidated
void json_object_set_n(JSON_Object *,const char * key,size_t n,JSON_Element * elem);//elem pointer is invalidated

void json_free_object(JSON_Object *);

typedef struct JSON_Array {
    JSON_Element_Type type;
    size_t size;
    size_t alloc;
    JSON_Element * arr;
} JSON_Array;

JSON_Array * json_make_array();

void json_free_array(JSON_Array *);

JSON_Element * json_array_get(JSON_Array * arr,size_t index);//pointers returned from this are 'fragile' they may break when modifying the array

void json_array_set(JSON_Array * arr,JSON_Element * elem,size_t index);//elem pointer is invalidated

void json_array_push(JSON_Array * arr,JSON_Element * elem);//elem pointer is invalidated

int json_array_insert(JSON_Array * arr,JSON_Element * elem,size_t index);//if returns 1, insertion failed and passed elem pointer is still valid, otherwise elem pointer is invalidated

void json_array_remove(JSON_Array * arr,size_t index);

typedef struct JSON_String {
    JSON_Element_Type type;
    size_t len;
    char * str;
} JSON_String;

JSON_String * json_make_string(const char * s);

JSON_String * json_make_string_n(const char * s,size_t n);

void json_set_string(JSON_String * str,const char * s);

void json_set_string_n(JSON_String * str,const char * s,size_t n);

void json_free_string(JSON_String *);

typedef struct JSON_Integer {
    JSON_Element_Type type;
    int64_t i;
} JSON_Integer;

JSON_Integer * json_make_integer(int64_t i);

typedef struct JSON_Double {
    JSON_Element_Type type;
    double d;
} JSON_Double;

JSON_Double * json_make_double(double d);

struct JSON_Element {
    union {
        JSON_Element_Type type;
        JSON_Array _arr;
        JSON_Object _obj;
        JSON_String _str;
        JSON_Integer _int;
        JSON_Double _double;
    };
};

void json_free_element(JSON_Element *);

JSON_Element * json_parse_n(const char * s,size_t n);

JSON_Element * json_parse(const char * s);

void json_write_element(FILE *f,JSON_Element *,size_t indentation);
void json_write_object(FILE *f,JSON_Object *,size_t indentation);
void json_write_array(FILE *f,JSON_Array *,size_t indentation);
void json_write_string(FILE *f,JSON_String *,size_t indentation);

//same as json_write_*(stdout,...)

void json_print_element(JSON_Element *,size_t indentation);
void json_print_object(JSON_Object *,size_t indentation);
void json_print_array(JSON_Array *,size_t indentation);
void json_print_string(JSON_String *,size_t indentation);

#ifdef __cplusplus
}
#endif // __cplusplus

utils.h

#pragma once

#ifdef __cplusplus
#include <cstddef>
#include <cstdint>
#define NORETURN [[noreturn]]
extern "C" {
#else
#include <stddef.h>
#include <stdint.h>
#define NORETURN __attribute__((__noreturn__))
#endif // __cplusplus


size_t str_hash(const char * s);

NORETURN void err_exit(const char * fmt,...);

#ifdef __cplusplus
}
#endif // __cplusplus

json.c

#include "json.h"
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include <string.h>
#include <stddef.h>
#include <stdint.h>
#include <stdarg.h>

#define OOM_EXIT() err_exit("Out of Memory in %s",__func__)

static void json_cleanup_element(void * p);

typedef struct JSON_Object_Table_Elem {
    uint32_t size;
    uint32_t alloc;
    void * arr;
} JSON_Object_Table_Elem;

typedef struct JSON_Object_Table {
    uint32_t num_buckets;
    uint32_t item_size;
    JSON_Object_Table_Elem buckets[];
} JSON_Object_Table;

typedef JSON_Object_Table table;
typedef JSON_Object_Table_Elem table_elem;

static table * alloc_table(size_t num_buckets,size_t item_size){
    table * tbl=calloc(1,sizeof(table)+
                         (num_buckets*sizeof(table_elem)));
    tbl->num_buckets=num_buckets;
    tbl->item_size=item_size;
    return tbl;
}

static void table_cleanup(table * tbl,void (*cleanup)(void*)){
    uint32_t isz=tbl->item_size;
    for(uint32_t i=0;i<tbl->num_buckets;i++){
        if(tbl->buckets[i].arr){
            uint32_t sz=tbl->buckets[i].size*isz;
            for(uint32_t j=0;j<sz;j+=isz){
                if(cleanup) cleanup(&((uint8_t *)tbl->buckets[i].arr)[j]);
            }
            free(tbl->buckets[i].arr);
        }
    }
    free(tbl);
}

static void table_add_item(table * tbl,void * item,uint32_t (*hash)(void*)){
    table_elem * e=&tbl->buckets[hash(item)%tbl->num_buckets];
    if(e->arr){
        if(e->alloc==e->size){
            uint32_t new_alloc=e->alloc*2;//growth factor 2
            e->arr=realloc(e->arr,new_alloc*tbl->item_size);
            if(!e->arr){
                OOM_EXIT();
            }
            e->alloc=new_alloc;
        }
    }else{
        e->arr=calloc(4,tbl->item_size);
        e->alloc=4;
    }
    memcpy((uint8_t*)e->arr+((e->size++)*tbl->item_size),item,tbl->item_size);
}

static void * table_find_item(table * tbl,void * key,uint32_t (*hash)(void*),int (*compare)(void*,void*)){
    table_elem * e=&tbl->buckets[hash(key)%tbl->num_buckets];
    if(!(e->arr&&e->size)) return NULL;
    uint8_t * arr=e->arr;
    uint32_t isz=tbl->item_size;
    uint32_t sz=e->size*isz;
    for(uint32_t i=0;i<sz;i+=isz){
        if(compare(&arr[i],key)){
            return &arr[i];
        }
    }
    return NULL;
}

typedef struct JSON_ObjectEntry {
    char * key;
    JSON_Element elem;
} JSON_ObjectEntry;

JSON_Object * json_make_object(){
    JSON_Object * obj=&((JSON_Element*)calloc(1,sizeof(JSON_Element)))->_obj;
    obj->type=JSON_OBJECT;
    obj->tbl=alloc_table(32,sizeof(JSON_ObjectEntry));
    return obj;
}

static int json_object_find_compare_keys(void * item,void * key){
    return strcmp(((JSON_ObjectEntry*)item)->key,(const char *)key)==0;
}

static uint32_t json_object_item_hash(void * item){
    return str_hash(((JSON_ObjectEntry*)item)->key);
}

static uint32_t json_object_key_hash(void * key){
    return str_hash((const char *)key);
}

JSON_Element * json_object_get_n(JSON_Object * obj,const char * key,size_t n){
    JSON_ObjectEntry * entry=table_find_item(obj->tbl,(void*)key,json_object_key_hash,json_object_find_compare_keys);
    if(entry){
        return &entry->elem;
    }else{
        return NULL;
    }
}

JSON_Element * json_object_get(JSON_Object * obj,const char * key){
    return json_object_get_n(obj,key,strlen(key));
}

void json_object_set_n(JSON_Object * obj,const char * key,size_t n,JSON_Element * elem){
    JSON_ObjectEntry * entry=table_find_item(obj->tbl,(void*)key,json_object_key_hash,json_object_find_compare_keys);
    if(entry){
        json_cleanup_element(&entry->elem);
        memcpy(&entry->elem,elem,sizeof(JSON_Element));
    }else{
        JSON_ObjectEntry new_entry = {
            .key=calloc(n+1,sizeof(char)),
            .elem={{0}},
        };
        memcpy(new_entry.key,key,n);
        memcpy(&new_entry.elem,elem,sizeof(JSON_Element));
        table_add_item(obj->tbl,&new_entry,json_object_item_hash);
    }
    free(elem);
}

void json_object_set(JSON_Object * obj,const char * key,JSON_Element * elem){
    json_object_set_n(obj,key,strlen(key),elem);
}

static void json_cleanup_object(JSON_Object * obj){
    if(!obj)return;
    table_cleanup(obj->tbl,json_cleanup_element);
}

void json_free_object(JSON_Object * obj){
    if(!obj)return;
    json_cleanup_object(obj);
    free(obj);
}

JSON_Array * json_make_array(){
    JSON_Array * arr=&((JSON_Element*)calloc(1,sizeof(JSON_Element)))->_arr;
    arr->type=JSON_ARRAY;
    return arr;
}

static void json_cleanup_array(JSON_Array * arr){
    if(!arr)return;
    if(arr->arr){
        size_t sz=arr->size;
        for(size_t i=0;i<sz;i++){
            json_cleanup_element(&arr->arr[i]);
        }
        free(arr->arr);
    }
}

void json_free_array(JSON_Array * arr){
    if(!arr)return;
    json_cleanup_array(arr);
    free(arr);
}

JSON_Element * json_array_get(JSON_Array * arr,size_t index){
    if(index>arr->size){
        return NULL;
    }
    return arr->arr+index;
}

void json_array_set(JSON_Array * arr,JSON_Element * elem,size_t index){
    if(index>arr->size)return;
    json_cleanup_element(arr->arr+index);
    memcpy(arr->arr+index,elem,sizeof(JSON_Element));
    free(elem);
}

static void json_array_grow_by(JSON_Array * arr,size_t by){
    if(arr->alloc>=(arr->size+by))return;
    if(arr->arr){
        arr->arr=realloc(arr->arr,(arr->size+by)*sizeof(JSON_Element));
    }else{
        arr->arr=calloc(by,sizeof(JSON_Element));
    }
    if(!arr->arr){
        OOM_EXIT();
    }
}

void json_array_push(JSON_Array * arr,JSON_Element * elem){
    json_array_grow_by(arr,1);
    memcpy(arr->arr+arr->size,elem,sizeof(JSON_Element));
    ++arr->size;
    free(elem);
}

int json_array_insert(JSON_Array * arr,JSON_Element * elem,size_t index){
    if(arr->size>index){
        json_array_grow_by(arr,1);
        memmove(arr->arr+index+1,arr->arr+index,((arr->size-index)-1)*sizeof(JSON_Element));
        ++arr->size;
        memcpy(arr->arr+index,elem,sizeof(JSON_Element));
        free(elem);
    }else if(arr->size==index){
        json_array_grow_by(arr,1);
        ++arr->size;
        memcpy(arr->arr+index,elem,sizeof(JSON_Element));
        free(elem);
    }else{
        //COULD NOT ADD, INVALID INDEX
        return 1;
    }
    return 0;
}

void json_array_remove(JSON_Array * arr,size_t index){
    if(arr->size>index){
        json_cleanup_element(arr->arr+index);
        --arr->size;
        if(arr->size>index) memmove(arr->arr+index,arr->arr+index+1,(arr->size-index)*sizeof(JSON_Element));
    }
}

JSON_String * json_make_string_n(const char * s,size_t n){
    JSON_String * str=&((JSON_Element*)calloc(1,sizeof(JSON_Element)))->_str;
    str->type=JSON_STRING;
    str->str=calloc(n+1,sizeof(char));
    memcpy(str->str,s,n);
    str->str[n]=0;
    str->len=n;
    return str;
}

JSON_String * json_make_string(const char * s){
    return json_make_string_n(s,strlen(s));
}

void json_set_string_n(JSON_String * str,const char * s,size_t n){
    free(str->str);
    str->str=calloc(n+1,sizeof(char));
    memcpy(str->str,s,n);
    str->str[n]=0;
    str->len=n;
}

void json_set_string(JSON_String * str,const char * s){
    json_set_string_n(str,s,strlen(s));
}

void json_cleanup_string(JSON_String * str){
    if(!str)return;
    free(str->str);
}

void json_free_string(JSON_String * str){
    if(!str)return;
    free(str->str);
    free(str);
}


JSON_Integer * json_make_integer(int64_t i){
    JSON_Integer * ie=&((JSON_Element*)calloc(1,sizeof(JSON_Element)))->_int;
    ie->type=JSON_INTEGER;
    ie->i=i;
    return ie;
}

JSON_Double * json_make_double(double d){
    JSON_Double * de=&((JSON_Element*)calloc(1,sizeof(JSON_Element)))->_double;
    de->type=JSON_DOUBLE;
    de->d=d;
    return de;
}

static void json_cleanup_element(void * p){
    if(!p)return;
    JSON_Element * elem=p;
    switch(elem->type){
    case JSON_ARRAY:
        json_cleanup_array(p);
        break;
    case JSON_OBJECT:
        json_cleanup_object(p);
        break;
    case JSON_PARSE_ERROR:
    case JSON_STRING:
        json_cleanup_string(p);
        break;
    default:
        break;
    }
}

void json_free_element(JSON_Element * elem){
    if(!elem)return;
    json_cleanup_element(elem);
    free(elem);
}

typedef struct parse_data {
    size_t i;
    size_t n;
    const char * s;
} parse_data;

JSON_Element * parse_error(const char * fmt,...){
    JSON_String * str=&((JSON_Element*)calloc(1,sizeof(JSON_Element)))->_str;
    va_list arg1,arg2;
    va_start(arg1,fmt);
    va_copy(arg2,arg1);
    size_t n=vsnprintf(NULL,0,fmt,arg2);
    va_end(arg2);
    str->type=JSON_PARSE_ERROR;
    str->str=calloc(n+1,sizeof(char));
    vsnprintf(str->str,n+1,fmt,arg1);
    va_end(arg1);
    str->str[n]=0;
    str->len=n;
    return (JSON_Element*)str;
}

static bool is_whitespace(char c){
    return c==' '||c=='\t'||c=='\n'||c=='\r';
}

void skip_whitespace(parse_data * p){
    while(p->i<p->n){
        if(is_whitespace(p->s[p->i])){
            ++p->i;
        }else if(p->s[p->i]=='/'&&(p->i+1<p->n)&&((p->s[p->i+1]=='/')||(p->s[p->i+1]=='*'))){
            if(p->s[p->i+1]=='/'){
                p->i+=2;
                while(p->i<p->n&&p->s[p->i]!='\n')++p->i;
                if(p->i<p->n)++p->i;
            }else{
                
                p->i+=2;
                if(p->i<p->n)++p->i;
                while(p->i<p->n&&p->s[p->i-1]!='*'&&p->s[p->i]!='/')++p->i;
                if(p->i<p->n)++p->i;
            }
        }else{
            break;
        }
    }
}

JSON_Element * json_parse(const char * s){
    return json_parse_n(s,strlen(s));
}

static char unescape(char c){
    switch(c) {
    case 'a':
        return '\a';
    case 'b':
        return '\b';
    case 't':
        return '\t';
    case 'n':
        return '\n';
    case 'v':
        return '\v';
    case 'f':
        return '\f';
    case 'r':
        return '\r';
    default:
        return c;
    }
}

static JSON_Element * json_parse_string(parse_data * p){
    skip_whitespace(p);
    bool singlequote=false;
    if(p->i>=p->n){
        return parse_error("Expected '\"', got EOF");
    }else if(p->s[p->i]=='\''){
        singlequote=true;
    }else if(p->s[p->i]!='"'){
        return parse_error("Expected '\"', got %c",p->s[p->i]);
    }
    ++p->i;
    bool reading_escape=false;
    size_t n=0,i=p->i;
    for(;i<p->n;++i){
        if(reading_escape){
            n++;
            reading_escape=false;
        }else if(p->s[i]=='\\'){
            reading_escape=true;
        }else if(p->s[i]==(singlequote?'\'':'"')){
            break;
        }else{
            n++;
        }
    }
    if(i>=p->n){
        return parse_error(reading_escape?"Expected '\"', got EOF":"Expected char got EOF");
    }
    JSON_String * str=&((JSON_Element*)calloc(1,sizeof(JSON_Element)))->_str;
    str->type=JSON_STRING;
    str->str=calloc(n+1,sizeof(char));
    str->str[n]=0;
    str->len=n;
    reading_escape=false;
    n=0;
    for(;p->i<p->n;++p->i){
        if(reading_escape){
            str->str[n++]=unescape(p->s[p->i]);
            reading_escape=false;
        }else if(p->s[p->i]=='\\'){
            reading_escape=true;
        }else if(p->s[p->i]==(singlequote?'\'':'"')){
            break;
        }else{
            str->str[n++]=p->s[p->i];
        }
    }
    ++p->i;
    return (JSON_Element *)str;
}

JSON_Element * json_parse_element(parse_data * p);

JSON_Element * json_parse_object(parse_data * p){
    skip_whitespace(p);
    if(p->i>=p->n){
        return parse_error("Expected '{', got EOF");
    }else if(p->s[p->i]!='{'){
        return parse_error("Expected '{', got %c",p->s[p->i]);
    }
    ++p->i;
    JSON_Object * obj=json_make_object();
    while(true){
        JSON_String * key=(JSON_String *)json_parse_string(p);
        if(key->type==JSON_PARSE_ERROR){
            json_free_object(obj);
            return (JSON_Element*)key;
        }
        skip_whitespace(p);
        if(p->i>=p->n){
            json_free_string(key);
            json_free_object(obj);
            return parse_error("Expected ':', got EOF");
        }else if(p->s[p->i]!=':'){
            json_free_string(key);
            json_free_object(obj);
            return parse_error("Expected ':', got %c",p->s[p->i]);
        }
        ++p->i;
        JSON_Element * e=json_parse_element(p);
        if(e->type==JSON_PARSE_ERROR){
            json_free_string(key);
            json_free_object(obj);
            return e;
        }
        json_object_set_n(obj,key->str,key->len,e);
        json_free_string(key);
        skip_whitespace(p);
        if(p->i>=p->n){
            json_free_object(obj);
            return parse_error("Expected '}', got EOF");
        }else if(p->s[p->i]==','){
            ++p->i;
            skip_whitespace(p);
            if(p->s[p->i]=='}'){
                ++p->i;
                return (JSON_Element*)obj;
            }
        }else if(p->s[p->i]=='}'){
            ++p->i;
            return (JSON_Element*)obj;
        }else{
            json_free_object(obj);
            return parse_error("Expected '}', got %c",p->s[p->i]);
        }
    }
    json_free_object(obj);
    return parse_error("Expected '}', got EOF");
}

JSON_Element * json_parse_array(parse_data * p){
    skip_whitespace(p);
    if(p->i>=p->n){
        return parse_error("Expected '[', got EOF");
    }else if(p->s[p->i]!='['){
        return parse_error("Expected '[', got %c",p->s[p->i]);
    }
    ++p->i;
    JSON_Array * arr=json_make_array();
    while(true){
        JSON_Element * e=json_parse_element(p);
        if(e->type==JSON_PARSE_ERROR){
            json_free_array(arr);
            return e;
        }
        json_array_push(arr,e);
        skip_whitespace(p);
        if(p->i>=p->n){
            json_free_array(arr);
            return parse_error("Expected ']', got EOF");
        }else if(p->s[p->i]==','){
            ++p->i;
            skip_whitespace(p);
            if(p->s[p->i]==']'){
                ++p->i;
                return (JSON_Element*)arr;
            }
        }else if(p->s[p->i]==']'){
            ++p->i;
            return (JSON_Element*)arr;
        }else{
            json_free_array(arr);
            return parse_error("Expected ']', got %c",p->s[p->i]);
        }
    }
    json_free_array(arr);
    return parse_error("Expected ']', got EOF");
}

typedef union numberdata {
    double d;
    int64_t i;
} numberdata;

JSON_Element * json_parse_number(parse_data * p){
    skip_whitespace(p);
    if(p->i>=p->n) return parse_error("Expected JSON Element, got EOF");
    bool is_double=false;
    bool is_negative=false;
    bool is_valid=false;
    size_t double_digit=1;
    numberdata number={0};
    switch(p->s[p->i]){
    case '+':
        is_negative=false;
        ++p->i;
        break;
    case '-':
        is_negative=true;
        ++p->i;
        break;
    case '.':
        is_double=true;
        number.d=0;
        ++p->i;
        break;
    default:
        if(p->s[p->i]<'0'||p->s[p->i]>'9'){
            return parse_error("Expected Number, got %c",p->s[p->i]);
        }
        break;
    }
    for(;p->i<p->n;++p->i){
        char c=p->s[p->i];
        if(c>='0'&&c<='9'){
            is_valid=true;
            if(is_double){
                number.d+=(c-'0')/pow(10,double_digit);
                double_digit++;
            }else{
                number.i*=10;
                number.i+=c-'0';
            }
        }else if(c=='.'){
            if(is_double){
                return parse_error("Expected Number, got %c",c);
            }
            is_double=true;
            number.d=number.i;
        }else if(is_valid){
            if(is_double){
                JSON_Double * d=&((JSON_Element*)calloc(1,sizeof(JSON_Element)))->_double;
                d->type=JSON_DOUBLE;
                d->d=is_negative?-number.d:number.d;
                return (JSON_Element*)d;
            }else{
                JSON_Integer * i=&((JSON_Element*)calloc(1,sizeof(JSON_Element)))->_int;
                i->type=JSON_INTEGER;
                i->i=is_negative?-number.i:number.i;
                return (JSON_Element*)i;
            }
        }else{
            return parse_error("Expected Number, got %c",c);
        }
    }
    if(is_valid){
        if(is_double){
            JSON_Double * d=&((JSON_Element*)calloc(1,sizeof(JSON_Element)))->_double;
            d->type=JSON_DOUBLE;
            d->d=is_negative?-number.d:number.d;
            return (JSON_Element*)d;
        }else{
            JSON_Integer * i=&((JSON_Element*)calloc(1,sizeof(JSON_Element)))->_int;
            i->type=JSON_INTEGER;
            i->i=is_negative?-number.i:number.i;
            return (JSON_Element*)i;
        }
    }
    return parse_error("Expected Number, got EOF");
}

JSON_Element * json_parse_element(parse_data * p){
    skip_whitespace(p);
    if(p->i>=p->n) return parse_error("Expected JSON Element, got EOF");
    char c=p->s[p->i];
    switch(c){
    case '{':
        return json_parse_object(p);
    case '[':
        return json_parse_array(p);
    case '"':
    case '\'':
        return json_parse_string(p);
    default:
        if((c>='0'&&c<='9')||c=='.'||c=='-'||c=='+'){
            return json_parse_number(p);
        }else if((p->i+4)<p->n&&p->s[p->i]=='f'&&p->s[p->i+1]=='a'&&p->s[p->i+2]=='l'&&p->s[p->i+3]=='s'&&p->s[p->i+3]=='e'){
            JSON_Element * e=calloc(1,sizeof(JSON_Element));
            e->type=JSON_FALSE;
            p->i+=5;
            return e;
        }else if((p->i+3)<p->n){
            if(p->s[p->i]=='t'&&p->s[p->i+1]=='r'&&p->s[p->i+2]=='u'&&p->s[p->i+3]=='e'){
                JSON_Element * e=calloc(1,sizeof(JSON_Element));
                e->type=JSON_TRUE;
                p->i+=4;
                return e;
            }else if(p->s[p->i]=='n'&&p->s[p->i+1]=='u'&&p->s[p->i+2]=='l'&&p->s[p->i+3]=='l'){
                JSON_Element * e=calloc(1,sizeof(JSON_Element));
                e->type=JSON_NULL;
                p->i+=4;
                return e;
            }
        }
        return parse_error("Expected JSON Element, got %c",c);
    }
}

JSON_Element * json_parse_n(const char * data,size_t len){
    parse_data p = {.i=0,.s=data,.n=len};
    return json_parse_element(&p);
}

void write_indent(FILE * f,size_t indentation){
    for(size_t i=0;i<indentation;i++){
        fputs("  ",f);
    }
}

void json_write_element(FILE * f,JSON_Element * elem,size_t indentation){
    switch(elem->type){
    case JSON_ARRAY:
        json_write_array(f,&elem->_arr,indentation);
        break;
    case JSON_OBJECT:
        json_write_object(f,&elem->_obj,indentation);
        break;
    case JSON_STRING:
        json_write_string(f,&elem->_str,indentation);
        break;
    case JSON_INTEGER:
        #if ULONG_MAX == 0xFFFFFFFFFFFFFFFFUL
        fprintf(f,"%ld",elem->_int.i);
        #elif ULONG_LONG_MAX == 0xFFFFFFFFFFFFFFFFUL
        fprintf(f,"%lld",elem->_int.i);
        #else
            #error "Can't print 64-bit integer"
        #endif
        break;
    case JSON_DOUBLE:
        fprintf(f,"%f",elem->_double.d);
        break;
    case JSON_TRUE:
        fprintf(f,"true");
        break;
    case JSON_FALSE:
        fprintf(f,"false");
        break;
    case JSON_NULL:
        fprintf(f,"null");
        break;
    case JSON_PARSE_ERROR:
        fprintf(f,"PARSE ERROR: %s",elem->_str.str);
        break;
    }
}

static bool needs_escape(char c){
    switch(c){
    case '\a':
    case '\b':
    case '\t':
    case '\n':
    case '\v':
    case '\f':
    case '\r':
    case '\\':
    case '\"':
    case '\'':
        return true;
    default:
        return false;
    }
}

static char escape(char c){
    switch(c){
    case '\a':
        return 'a';
    case '\b':
        return 'b';
    case '\t':
        return 't';
    case '\n':
        return 'n';
    case '\v':
        return 'v';
    case '\f':
        return 'f';
    case '\r':
        return 'r';
    default:
        return c;
    }
}

static void write_quoted(FILE * f,const char * str){
    fputc('"',f);
    char c;
    while((c=*(str++))){
        if(needs_escape(c)){
            fputc('\\',f);
            fputc(escape(c),f);
        }else{
            fputc(c,f);
        }
    }
    fputc('"',f);
}

void json_write_object(FILE * f,JSON_Object * obj,size_t indentation){
    fputc('{',f);
    bool first=true;
    for(uint32_t i=0;i<obj->tbl->num_buckets;i++){
        if(obj->tbl->buckets[i].size&&obj->tbl->buckets[i].arr){
            JSON_ObjectEntry * arr=obj->tbl->buckets[i].arr;
            for(uint32_t j=0;j<obj->tbl->buckets[i].size;j++){
                if(first){
                    first=false;
                    fputc('\n',f);
                }else{
                    fputc(',',f);
                    fputc('\n',f);
                }
                write_indent(f,indentation+1);
                write_quoted(f,arr[j].key);
                fputc(':',f);
                json_write_element(f,&arr[j].elem,indentation+1);
            }
        }
    }
    if(!first){
        fputc('\n',f);
        write_indent(f,indentation);
        fputc('}',f);
    }else{
        fputc('}',f);
    }

}

void json_write_array(FILE * f,JSON_Array * arr,size_t indentation){
    fputc('[',f);
    bool first=true;
    if(arr->size&&arr->arr){
        JSON_Element * a=arr->arr;
        for(uint32_t i=0;i<arr->size;i++){
            if(first){
                first=false;
                fputc('\n',f);
            }else{
                fputc(',',f);
                fputc('\n',f);
            }
            write_indent(f,indentation+1);
            json_write_element(f,&a[i],indentation+1);
        }
    }
    if(!first){
        fputc('\n',f);
        write_indent(f,indentation);
        fputc(']',f);
    }else{
        fputc(']',f);
    }
}

void json_write_string(FILE * f,JSON_String * str,size_t indentation){
    write_quoted(f,str->str);
}

void json_print_element(JSON_Element * elem,size_t indentation){
    json_write_element(stdout,elem,indentation);
}

void json_print_object(JSON_Object * obj,size_t indentation){
    json_write_object(stdout,obj,indentation);
}

void json_print_array(JSON_Array * arr,size_t indentation){
    json_write_array(stdout,arr,indentation);
}

void json_print_string(JSON_String * str,size_t indentation){
    json_write_string(stdout,str,indentation);
}

utils.c

#include "utils.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

size_t str_hash(const char * s){
    size_t hash = 5381;
    // hash * 33 + c
    while(*s++)hash = ((hash << 5) + hash) + ((size_t)*s);
    return hash;
}

void err_exit(const char * fmt,...){
    va_list arg;
    va_start(arg,fmt);
    vfprintf(stderr,fmt,arg);
    va_end(arg);
    exit(1);
}

Test Code, C++ for simplicity

main.cpp

#include "json.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <cerrno>
#include <cstring>

static std::string readfile(const std::string &filename){
    std::ostringstream ss;
    std::ifstream f(filename);
    if(!f)throw std::runtime_error(strerror(errno));
    ss<<f.rdbuf();
    return ss.str();
}

int main() {
    std::string file=readfile("test.json");
    JSON_Element * elem=json_parse_n(file.c_str(),file.size());
    FILE * f=fopen("test_out.json","w");
    if(!f){
        json_free_element(elem);
        throw std::runtime_error(strerror(errno));
    }
    json_write_element(f,elem,0);
    json_free_element(elem);
    fclose(f);
    return 0;
}

Code is also available on Github

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Impenetrable code

I love C, but lines like these make me sad. They look like a strawman caricature of C designed by someone who wants to illustrate that it's a bad idea.

memcpy((uint8_t*)e->arr+((e->size++)*tbl->item_size),item,tbl->item_size);

    memmove(arr->arr+index+1,arr->arr+index,((arr->size-index)-1)*sizeof(JSON_Element));

    if(arr->size>index) memmove(arr->arr+index,arr->arr+index+1,(arr->size-index)*sizeof(JSON_Element));

    }else if(p->s[p->i]=='/'&&(p->i+1<p->n)&&((p->s[p->i+1]=='/')||(p->s[p->i+1]=='*'))){

and so on. I promise you that readability will increase and performance will not decrease by the introduction of whitespace and temporary variables.

Lookup arrays

static char escape(char c){
    switch(c){
    case '\a':
        return 'a';
    case '\b':
        return 'b';
    case '\t':
        return 't';
    case '\n':
        return 'n';
    case '\v':
        return 'v';
    case '\f':
        return 'f';
    case '\r':
        return 'r';
    default:
        return c;
    }
}

can be re-modelled as a linear array of 256 characters. This can be initialized as a literal or at program start.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.