dbutils/json.c

Tue, 06 Jan 2026 20:50:10 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Tue, 06 Jan 2026 20:50:10 +0100
changeset 45
a8b1df52e63e
parent 44
3bac2715ccb9
child 46
96e139ab57f2
permissions
-rw-r--r--

add dbuJsonToList

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2025 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>

#include "json.h"

/* ---------------- Obj to JSON serialization functions ---------------------*/

CxJsonValue* dbuObjectToJson(DBUClass *type, void *obj, const CxAllocator *a) {
    return dbuObjectToJson2(type, obj, a, false, false);
}

CxJsonValue* dbuObjectToJson2(DBUClass *type, void *obj, const CxAllocator *a, bool setNull, bool forceString) {
    if(obj == NULL) {
        return cxJsonCreateLiteral(a, CX_JSON_NULL);
    }
    if(!a) {
        a = cxDefaultAllocator;
    }
    
    CxJsonValue *value = cxJsonCreateObj(a);
    if(!value) {
        return NULL;
    }
    
    // add all primitive values
    CxMapIterator i = cxMapIteratorValues(type->fields);
        for(int n=0;n<2;n++) {
            cx_foreach(DBUField *, field, i) {
            CxJsonValue *child = NULL;
            if(field->toBool) {
                bool b = field->toBool(field, obj);
                child = cxJsonCreateLiteral(a, b ? CX_JSON_TRUE : CX_JSON_FALSE);
            } else if(field->toInt64) {
                int64_t i = field->toInt64(field, obj);
                child = cxJsonCreateInteger(a, i);
            } else if(field->toUInt64) {
                uint64_t u = field->toUInt64(field, obj);
                child = cxJsonCreateInteger(a, (int64_t)u);
            } else if(field->toDouble) {
                double d = field->toDouble(field, obj);
                child = cxJsonCreateNumber(a, d);
            } else if(field->toString) {
                cxmutstr s = field->toString(field, obj, a);
                if(s.ptr) {
                    // we don't want to copy the string again, therefore
                    // we can't use cxJsonCreateString
                    child = cxMalloc(a, sizeof(CxJsonValue));
                    if(!child) {
                        cxFree(a, s.ptr);
                        return NULL;
                    }
                    child->allocator = a;
                    child->type = CX_JSON_STRING;
                    child->string = s;
                }
            } else if(field->toBinary) {
                // TODO
            } else if(field->toObject) {
                DBUObject child_obj = field->toObject(field, obj);
                if(child_obj == NULL) {
                    child = CX_JSON_NULL;
                } else if(field->objType) {
                    child = dbuObjectToJson2(field->objType, child_obj, a, setNull, forceString);
                }
            } else if(field->toList) {
                DBUAbstractList *list = field->toList(field, obj);
                child = cxJsonCreateArr(a, list->length(list));
                if(child) {
                    if(list->iterator) {
                        CxIterator iter = list->iterator(list);
                        cx_foreach(void *, elm, iter) {
                            DBUField *f = list->elementField(list, elm);
                            if(f->toObject) {
                                DBUObject child_obj = f->toObject(field, elm);
                                if(f->objType) {
                                    CxJsonValue *array_elm = dbuObjectToJson2(f->objType, child_obj, a, setNull, forceString);
                                    cxJsonArrAddValues(child, &array_elm, 1);
                                }
                            }
                        }
                    }
                }
            } else {
                continue; // non-serializable field
            }

            if(!child) {
                cxJsonValueFree(value);
                return NULL;
            }

            if(cxJsonObjPut(value, field->name, child)) {
                cxJsonValueFree(child);
                cxJsonValueFree(value);
                return NULL;
            }
        }
        i = cxMapIteratorValues(type->obj_fields);
    }
    
    return value;
}


/* --------------- Json to Obj deserialization functions --------------------*/

static void* jsonToObj(DBUClass *type, const CxAllocator *a, CxJsonValue *value, int depth, int *error) {
    if(!cxJsonIsObject(value)) {
        *error = 1;
        return NULL;
    }
    if(depth > DBU_JSON_MAX_DEPTH) {
        *error = 2;
        return NULL;
    }
    if(!a) {
        a = cxDefaultAllocator;
    }
     
    void *obj = cxMalloc(a, type->obj_size);
    if(obj) {
        memset(obj, 0, type->obj_size);
    } else {
        *error = 3;
        return NULL;
    }
    
    char buf[64];
    int len = 0;
    
    CxMapIterator i = cxMapIterator(value->object);
    cx_foreach(CxMapEntry *, entry, i) {
        DBUField *field = cxMapGet(type->fields, entry->key);
        if(!field) {
            field = cxMapGet(type->obj_fields, entry->key);
            if(!field) {
                continue;
            }
        }
        
        len = 0;
        
        CxJsonValue *child = entry->value;
        switch(child->type) {
            case CX_JSON_LITERAL: {
                if(child->literal == CX_JSON_NULL) {
                    if(field->initObjValue) {
                        field->initObjValue(field, a, obj, NULL);
                    }
                    break;
                } else {
                    int b = child->literal == CX_JSON_TRUE;
                    if(field->initIntValue) {
                        field->initIntValue(field, a, obj, b);
                        continue;
                    } else {
                        len = snprintf(buf, 64, "%d", b);
                    }
                }
                break;
            }
            case CX_JSON_INTEGER: {
                if(field->initIntValue) {
                    field->initIntValue(field, a, obj, child->integer);
                } else {
                    len = snprintf(buf, 64, "%" PRId64, child->integer);
                }
                break;
            }
            case CX_JSON_NUMBER: {
                if(field->initDoubleValue) {
                    field->initDoubleValue(field, a, obj, child->number);
                } else {
                    len = snprintf(buf, 64, "%f", child->number);
                }
                break;
            }
            case CX_JSON_STRING: {
                cxmutstr str = child->string;
                if(field->initValue(field, a, obj, str.ptr, str.length)) {
                    *error = 3;
                    free(obj); // TODO: improve obj cleanup
                    return NULL;
                }
                break;
            }
            case CX_JSON_OBJECT: {
                if(field->objType && field->initObjValue) {
                    void *child_obj = jsonToObj(field->objType, a, child, depth+1, error);
                    if(child_obj) {
                        field->initObjValue(field, a, obj, child_obj);
                    } else {
                        free(obj); // TODO: improve obj cleanup
                        return NULL;
                    }
                }
                break;
            }
            case CX_JSON_ARRAY: {
                if(field->builder.add) {
                    for(int i=0;i<child->array.size;i++) {
                        CxJsonValue *elm = child->array.data[i];
                        void *elm_obj = NULL;
                        bool add_elm = false;
                        if(cxJsonIsObject(elm)) {
                            elm_obj = jsonToObj(field->builder.type, a, elm, depth+1, error);
                            if(!elm_obj) {
                                free(obj); // TODO: improve obj cleanup
                                return NULL;
                            }
                            add_elm = true;
                            
                        } else if(cxJsonIsLiteral(elm) && elm->literal == CX_JSON_NULL) {
                            add_elm = true;
                        }
                        
                        if(add_elm) {
                            if(field->builder.add(&field->builder, obj, field->builder.type, elm_obj, NULL, a)) {
                                free(obj); // TODO: improve obj cleanup
                                free(elm_obj);
                                *error = 3;
                                return NULL;
                            }
                        }
                    }
                }
                break;
            }
            default: break;
        }
        
        if(len > 0) {
            if(field->initValue(field, a, obj, buf, len)) {
                // TODO: completely cleanup obj
                free(obj);
                *error = 3;
                return NULL;
            }
        }
    }
    
    *error = 0;
    return obj;
}

void* dbuJsonToObject(DBUClass *type, const CxAllocator *a, CxJsonValue *value) {
    int error;
    return jsonToObj(type, a, value, 0, &error);
}

static CxList* jsonToArray(DBUClass *type, const CxAllocator *a, CxJsonValue *value, size_t elmSize, int *error) {
    if(!cxJsonIsArray(value)) {
        *error = 1;
        return NULL;
    }
    
    CxList *list = cxArrayListCreate(a, elmSize, value->array.size);
    if(!list) {
        *error = 3;
    }
    
    for(size_t i=0;i<value->array.size;i++) {
        void *elm = jsonToObj(type, a, value->array.data[i], 1, error);
        if(!elm) {
            cxListFree(list);
            return NULL;
        }
        if(cxListAdd(list, elm)) {
            *error = 3;
            cxListFree(list);
            return NULL;
        }
    }
    
    return list;
}

CxList* dbuJsonToList(DBUClass *type, const CxAllocator *a, CxJsonValue *value) {
    int error;
    return jsonToArray(type, a, value, CX_STORE_POINTERS, &error);
}

mercurial