implement json child object serialization default tip

Wed, 10 Dec 2025 18:36:02 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 10 Dec 2025 18:36:02 +0100
changeset 28
e46f9f254fcd
parent 27
5baee70feaa9

implement json child object serialization

dbutils/dbutils/dbutils.h file | annotate | diff | comparison | revisions
dbutils/field.c file | annotate | diff | comparison | revisions
dbutils/json.c file | annotate | diff | comparison | revisions
dbutils/json.h file | annotate | diff | comparison | revisions
test/json.c file | annotate | diff | comparison | revisions
test/json.h file | annotate | diff | comparison | revisions
test/main.c file | annotate | diff | comparison | revisions
--- a/dbutils/dbutils/dbutils.h	Tue Dec 09 19:40:08 2025 +0100
+++ b/dbutils/dbutils/dbutils.h	Wed Dec 10 18:36:02 2025 +0100
@@ -143,6 +143,39 @@
 };
 
 /*
+ * list abstraction for accessing elements
+ */
+typedef struct DBUAbstractList DBUAbstractList;
+struct DBUAbstractList {
+    /*
+     * List pointer (for example a C array or CxList*)
+     */
+    void *list;
+    /*
+     * Get the list size
+     */
+    size_t (*length)(DBUAbstractList *ls);
+    /*
+     * Get the element at the specified index
+     */
+    void* (*get)(DBUAbstractList *ls, size_t index);
+    /*
+     * Get an DBUField for an list element. The DBUField pointer is only
+     * valid until another elementField call.
+     */
+    DBUField* (*elementField)(DBUAbstractList *ls, void *elm);
+    /*
+     * Optional function for getting an list iterator
+     */
+    CxIterator (*iterator)(DBUAbstractList *ls);
+    /*
+     * Free the DBUAbstractList abstraction layer. The actual list is
+     * not freed.
+     */
+    void (*free)(DBUAbstractList *ls);
+};
+
+/*
  * abstract field
  */
 struct DBUField {
@@ -152,6 +185,11 @@
     cxmutstr name;
     
     /*
+     * field object type
+     */
+    DBUClass *objType;
+    
+    /*
      * called, if the field is null (optional)
      */
     int (*initDefaultValue)(DBUField *f, const CxAllocator *a, DBUObject obj);
@@ -230,6 +268,21 @@
     double (*toDouble)(DBUField *f, DBUObject obj);
     
     /*
+     * get the field value as object (optional)
+     * 
+     * This should only be implemented, if the field has a compley type
+     * and objType is set.
+     */
+    DBUObject (*toObject)(DBUField *f, DBUObject obj);
+    
+    /*
+     * get the field value as a list/iterator (optional)
+     * 
+     * This should only be implemented, if the field is a list.
+     */
+    DBUAbstractList (*toList)(DBUField *f);
+    
+    /*
      * destructor (optional)
      * 
      * this callback must not free the DBUField* ptr
--- a/dbutils/field.c	Tue Dec 09 19:40:08 2025 +0100
+++ b/dbutils/field.c	Wed Dec 10 18:36:02 2025 +0100
@@ -1201,10 +1201,17 @@
     return 0;
 }
 
+static DBUObject get_obj_ptr(DBUField *f, DBUObject obj) {
+    DBUOffsetField *field = (DBUOffsetField*)f;
+    return *(void**)(obj+field->offset);
+}
+
 void dbuClassAddObj(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls) {
     DBUOffsetField *field = malloc(sizeof(DBUOffsetField));
     memset(field, 0, sizeof(DBUOffsetField));
     field->field.initObjValue = set_obj_ptr;
+    field->field.toObject = get_obj_ptr;
+    field->field.objType = foreign_cls;
     field->offset = offset;
     field->def.def = 0;
     dbuClassAddObjField(cls, name, (DBUField*)field, foreign_cls);
--- a/dbutils/json.c	Tue Dec 09 19:40:08 2025 +0100
+++ b/dbutils/json.c	Wed Dec 10 18:36:02 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2024 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -47,50 +47,62 @@
     
     // add all primitive values
     CxMapIterator i = cxMapIteratorValues(type->fields);
-    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;
+        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->value.string = s;
                 }
-                child->allocator = a;
-                child->type = CX_JSON_STRING;
-                child->value.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) {
+                // TODO
+            } else {
+                continue; // non-serializable field
             }
-        } else if(field->toBinary) {
-            // TODO
-        } else {
-            continue; // non-serializable field
+
+            if(!child) {
+                cxJsonValueFree(value);
+                return NULL;
+            }
+
+            if(cxJsonObjPut(value, field->name, child)) {
+                cxJsonValueFree(child);
+                cxJsonValueFree(value);
+                return NULL;
+            }
         }
-        
-        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;
--- a/dbutils/json.h	Tue Dec 09 19:40:08 2025 +0100
+++ b/dbutils/json.h	Wed Dec 10 18:36:02 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2024 Olaf Wintermann. All rights reserved.
+ * 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:
--- a/test/json.c	Tue Dec 09 19:40:08 2025 +0100
+++ b/test/json.c	Wed Dec 10 18:36:02 2025 +0100
@@ -48,8 +48,27 @@
     double d;
 } Test1;
 
+typedef struct Test2 {
+    char *name;
+    int i;
+} Test2;
+
+typedef struct Test3 {
+    char *test3;
+    Test2 *test2;
+} Test3;
+
+typedef struct Test4 {
+    int id;
+    Test3 *test3;
+    Test2 *test2;
+} Test4;
+
 static DBUContext *ctx;
 static DBUClass *test1_class;
+static DBUClass *test2_class;
+static DBUClass *test3_class;
+static DBUClass *test4_class;
 
 int init_json_tests(void) {
     ctx = dbuContextCreate();
@@ -68,6 +87,19 @@
     dbuClassAdd(test1_class, Test1, bt);
     dbuClassAdd(test1_class, Test1, bf);
     dbuClassAdd(test1_class, Test1, d);
+    
+    test2_class = dbuRegisterClassWithoutPK(ctx, "test2", sizeof(Test2));
+    dbuClassAdd(test2_class, Test2, name);
+    dbuClassAdd(test2_class, Test2, i);
+    
+    test3_class = dbuRegisterClassWithoutPK(ctx, "test3", sizeof(Test3));
+    dbuClassAdd(test3_class, Test3, test3);
+    dbuClassAddObj(test3_class, "test2", offsetof(Test3, test2), test2_class);
+    
+    test4_class = dbuRegisterClassWithoutPK(ctx, "test4", sizeof(Test4));
+    dbuClassAdd(test4_class, Test4, id);
+    dbuClassAddObj(test4_class, "test2", offsetof(Test4, test2), test2_class);
+    dbuClassAddObj(test4_class, "test3", offsetof(Test4, test3), test3_class);
 }
 
 void cleanup_json_tests(void) {
@@ -94,7 +126,7 @@
         CxJsonValue *value = dbuObjectToJson(test1_class, &test, NULL);
         
         CX_TEST_ASSERT(value);
-        CX_TEST_ASSERT(value->type == CX_JSON_OBJECT);
+        CX_TEST_ASSERT(cxJsonIsObject(value));
         
         CxJsonValue *str = cxJsonObjGet(value, "str");
         CxJsonValue *str2 = cxJsonObjGet(value, "str2");
@@ -133,6 +165,77 @@
         CX_TEST_ASSERT(i64->value.integer == test.i64);
         CX_TEST_ASSERT(u64->value.integer == test.u64);
         CX_TEST_ASSERT(d->value.number < test.d + 0.1 && d->value.number > test.d - 0.1);
+        
+        cxJsonValueFree(value);
     }
 }
 
+CX_TEST(testObjectToJsonChildObj) {
+    Test2 t2_1;
+    t2_1.i = 12;
+    t2_1.name = "t2_1";
+    
+    Test2 t2_2;
+    t2_2.i = 45;
+    t2_2.name = "t2_2";
+    
+    Test3 t3;
+    t3.test3 = "t3";
+    t3.test2 = &t2_1;
+    
+    Test4 t4;
+    t4.id = 15;
+    t4.test2 = &t2_2;
+    t4.test3 = &t3;
+    
+    CX_TEST_DO {
+        CxJsonValue *value = dbuObjectToJson(test4_class, &t4, NULL);
+        
+        CX_TEST_ASSERT(value);
+        CX_TEST_ASSERT(cxJsonIsObject(value));
+        
+        CxJsonValue *v = cxJsonObjGet(value, "id");
+        CX_TEST_ASSERT(v);
+        CX_TEST_ASSERT(cxJsonIsInteger(v));
+        CX_TEST_ASSERT(v->value.integer == t4.id);
+        
+        v = cxJsonObjGet(value, "test2");
+        CX_TEST_ASSERT(v);
+        CX_TEST_ASSERT(cxJsonIsObject(v));
+        
+        CxJsonValue *s = cxJsonObjGet(v, "i");
+        CX_TEST_ASSERT(s);
+        CX_TEST_ASSERT(cxJsonIsInteger(s));
+        CX_TEST_ASSERT(s->value.integer == t2_2.i);
+        
+        s = cxJsonObjGet(v, "name");
+        CX_TEST_ASSERT(s);
+        CX_TEST_ASSERT(cxJsonIsString(s));
+        CX_TEST_ASSERT(!cx_strcmp(s->value.string, t2_2.name));
+        
+        v = cxJsonObjGet(value, "test3");
+        CX_TEST_ASSERT(v);
+        CX_TEST_ASSERT(cxJsonIsObject(v));
+        
+        s = cxJsonObjGet(v, "test3");
+        CX_TEST_ASSERT(s);
+        CX_TEST_ASSERT(cxJsonIsString(s));
+        CX_TEST_ASSERT(!cx_strcmp(s->value.string, t3.test3));
+        
+        s = cxJsonObjGet(v, "test2");
+        CX_TEST_ASSERT(s);
+        CX_TEST_ASSERT(cxJsonIsObject(s));
+        
+        CxJsonValue *x = cxJsonObjGet(s, "i");
+        CX_TEST_ASSERT(x);
+        CX_TEST_ASSERT(cxJsonIsInteger(x));
+        CX_TEST_ASSERT(x->value.integer == t2_1.i);
+        
+        x = cxJsonObjGet(s, "name");
+        CX_TEST_ASSERT(x);
+        CX_TEST_ASSERT(cxJsonIsString(x));
+        CX_TEST_ASSERT(!cx_strcmp(x->value.string, t2_1.name));
+        
+        cxJsonValueFree(value);
+    }
+}
--- a/test/json.h	Tue Dec 09 19:40:08 2025 +0100
+++ b/test/json.h	Wed Dec 10 18:36:02 2025 +0100
@@ -41,6 +41,7 @@
 void cleanup_json_tests(void);
 
 CX_TEST(testObjectToJsonSimple);
+CX_TEST(testObjectToJsonChildObj);
 
 
 #ifdef __cplusplus
--- a/test/main.c	Tue Dec 09 19:40:08 2025 +0100
+++ b/test/main.c	Wed Dec 10 18:36:02 2025 +0100
@@ -135,6 +135,7 @@
     cx_test_register(suite, testSingleTableQuery);
 #endif
     cx_test_register(suite, testObjectToJsonSimple);
+    cx_test_register(suite, testObjectToJsonChildObj);
     
     cx_test_run_stdout(suite);
     

mercurial