This is an automated email from the ASF dual-hosted git repository.

pnoltes pushed a commit to branch feature/685-properties-json-serialization
in repository https://gitbox.apache.org/repos/asf/celix.git

commit 9511610bb2c7379570c1bd3a9705202c0723a865
Author: Pepijn Noltes <pnol...@apache.org>
AuthorDate: Tue Apr 2 20:25:09 2024 +0200

    gh-685: Add jansson dep for simple properties save/load
---
 libs/utils/CMakeLists.txt                          |   3 +-
 .../gtest/src/PropertiesSerializationTestSuite.cc  |  70 ++++++++++++-
 libs/utils/src/properties.c                        |   2 +-
 libs/utils/src/properties_serialization.c          | 112 ++++++++++++++++++++-
 4 files changed, 180 insertions(+), 7 deletions(-)

diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt
index 064c9683..4180b48b 100644
--- a/libs/utils/CMakeLists.txt
+++ b/libs/utils/CMakeLists.txt
@@ -18,6 +18,7 @@
 celix_subproject(UTILS "Option to enable building the Utilities library" ON)
 if (UTILS)
     find_package(libzip REQUIRED)
+    find_package(jansson REQUIRED) #TODO add jansson dep info to build (conan) 
and documentation info
 
     set(MEMSTREAM_SOURCES )
     set(MEMSTREAM_INCLUDES )
@@ -42,7 +43,7 @@ if (UTILS)
             src/celix_cleanup.c
             ${MEMSTREAM_SOURCES}
             )
-    set(UTILS_PRIVATE_DEPS libzip::zip)
+    set(UTILS_PRIVATE_DEPS libzip::zip jansson::jansson)
     set(UTILS_PUBLIC_DEPS)
 
     add_library(utils SHARED ${UTILS_SRC})
diff --git a/libs/utils/gtest/src/PropertiesSerializationTestSuite.cc 
b/libs/utils/gtest/src/PropertiesSerializationTestSuite.cc
index d3bf6aa0..99cbeceb 100644
--- a/libs/utils/gtest/src/PropertiesSerializationTestSuite.cc
+++ b/libs/utils/gtest/src/PropertiesSerializationTestSuite.cc
@@ -18,9 +18,11 @@
  */
 
 #include <gtest/gtest.h>
+#include <jansson.h>
 
 #include "celix_err.h"
 #include "celix_properties.h"
+#include "celix_stdlib_cleanup.h"
 
 using ::testing::MatchesRegex;
 
@@ -34,7 +36,7 @@ TEST_F(PropertiesSerializationTestSuite, 
SaveEmptyPropertiesTest) {
     celix_autoptr(celix_properties_t) props = celix_properties_create();
 
     //And an in-memory stream
-    char* buf = nullptr;
+    celix_autofree char* buf = nullptr;
     size_t bufLen = 0;
     FILE* stream = open_memstream(&buf, &bufLen);
 
@@ -62,3 +64,69 @@ TEST_F(PropertiesSerializationTestSuite, 
LoadEmptyPropertiesTest) {
 
     fclose(stream);
 }
+
+TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithSingelValuesTest) {
+        //Given a properties object with single values
+        celix_autoptr(celix_properties_t) props = celix_properties_create();
+        celix_properties_set(props, "key1", "value1");
+        celix_properties_set(props, "key2", "value2");
+        celix_properties_setLong(props, "key3", 3);
+        celix_properties_setDouble(props, "key4", 4.0);
+        celix_properties_setBool(props, "key5", true);
+        celix_properties_assignVersion(props, "key6", celix_version_create(1, 
2, 3, "qualifier"));
+
+        //And an in-memory stream
+        celix_autofree char* buf = nullptr;
+        size_t bufLen = 0;
+        FILE* stream = open_memstream(&buf, &bufLen);
+
+        //When saving the properties to the stream
+        auto status = celix_properties_saveToStream(props, stream);
+        EXPECT_EQ(CELIX_SUCCESS, status);
+
+        //Then the stream contains the JSON representation snippets of the 
properties
+        fclose(stream);
+        EXPECT_NE(nullptr, strstr(buf, R"("key1":"value1")")) << "JSON: " << 
buf;
+        EXPECT_NE(nullptr, strstr(buf, R"("key2":"value2")")) << "JSON: " << 
buf;
+        EXPECT_NE(nullptr, strstr(buf, R"("key3":3)")) << "JSON: " << buf;
+        EXPECT_NE(nullptr, strstr(buf, R"("key4":4.0)")) << "JSON: " << buf;
+        EXPECT_NE(nullptr, strstr(buf, R"("key5":true)")) << "JSON: " << buf;
+
+        //TODO how are versions serialized? A string representation is needed 
to reconstruct the version from JSON
+        EXPECT_NE(nullptr, strstr(buf, 
R"("key6":"celix_version<1.2.3.qualifier>")")) << "JSON: " << buf;
+
+        //And the buf is a valid JSON object
+        json_error_t error;
+        json_t* root = json_loads(buf, 0, &error);
+        EXPECT_NE(nullptr, root) << "Unexpected JSON error: " << error.text;
+        json_decref(root);
+}
+
+TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithArrayListsTest) {
+    //Given a properties object with array list values
+    celix_autoptr(celix_properties_t) props = celix_properties_create();
+    celix_array_list_t* list1 = celix_arrayList_createStringArray();
+    celix_arrayList_addString(list1, "value1");
+    celix_arrayList_addString(list1, "value2");
+    celix_properties_assignArrayList(props, "key1", list1);
+    //TODO long, double, bool, version
+
+    //And an in-memory stream
+    celix_autofree char* buf = nullptr;
+    size_t bufLen = 0;
+    FILE* stream = open_memstream(&buf, &bufLen);
+
+    //When saving the properties to the stream
+    auto status = celix_properties_saveToStream(props, stream);
+    EXPECT_EQ(CELIX_SUCCESS, status);
+
+    //Then the stream contains the JSON representation snippets of the 
properties
+    fclose(stream);
+    EXPECT_NE(nullptr, strstr(buf, R"("key1":["value1","value2"])")) << "JSON: 
" << buf;
+
+    //And the buf is a valid JSON object
+    json_error_t error;
+    json_t* root = json_loads(buf, 0, &error);
+    EXPECT_NE(nullptr, root) << "Unexpected JSON error: " << error.text;
+    json_decref(root);
+}
diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c
index c5100112..024d849d 100644
--- a/libs/utils/src/properties.c
+++ b/libs/utils/src/properties.c
@@ -645,7 +645,7 @@ const celix_properties_entry_t* 
celix_properties_getEntry(const celix_properties
     return entry;
 }
 
-static const bool celix_properties_isEntryArrayListWithElType(const 
celix_properties_entry_t* entry,
+static bool celix_properties_isEntryArrayListWithElType(const 
celix_properties_entry_t* entry,
                                                               
celix_array_list_element_type_t elType) {
     return entry != NULL && entry->valueType == 
CELIX_PROPERTIES_VALUE_TYPE_ARRAY_LIST &&
            celix_arrayList_getElementType(entry->typed.arrayValue) == elType;
diff --git a/libs/utils/src/properties_serialization.c 
b/libs/utils/src/properties_serialization.c
index 4ebda01e..d52fa6ef 100644
--- a/libs/utils/src/properties_serialization.c
+++ b/libs/utils/src/properties_serialization.c
@@ -19,11 +19,115 @@
 
 #include "celix_properties.h"
 
+#include "celix_err.h"
+#include "celix_stdlib_cleanup.h"
+
+#include <jansson.h>
+
+//TODO make jansson wrapper header for auto cleanup, wrap json_object_set_new 
and json_dumpf for error injection
+
+static json_t* celix_properties_versionToJson(const celix_version_t *version) {
+    celix_autofree char* versionStr = celix_version_toString(version); // TODO 
error handling
+    return json_sprintf("celix_version<%s>", versionStr);
+}
+
+static json_t* 
celix_properties_arrayElementEntryValueToJson(celix_array_list_element_type_t 
elType,
+                                                             
celix_array_list_entry_t entry) {
+    switch (elType) {
+    case CELIX_ARRAY_LIST_ELEMENT_TYPE_STRING:
+        return json_string(entry.stringVal);
+    case CELIX_ARRAY_LIST_ELEMENT_TYPE_LONG:
+        return json_integer(entry.longVal);
+    case CELIX_ARRAY_LIST_ELEMENT_TYPE_DOUBLE:
+        return json_real(entry.doubleVal);
+    case CELIX_ARRAY_LIST_ELEMENT_TYPE_BOOL:
+        return json_boolean(entry.boolVal);
+    case CELIX_ARRAY_LIST_ELEMENT_TYPE_VERSION:
+        celix_properties_versionToJson(entry.versionVal);
+    default:
+        // LCOV_EXCL_START
+        celix_err_pushf("Unexpected array list element type %d", elType);
+        return NULL;
+        // LCOV_EXCL_STOP
+    }
+}
+
+static json_t* celix_properties_arrayEntryValueToJson(const 
celix_properties_entry_t* entry) {
+    json_t* array = json_array();
+    if (!array) {
+        celix_err_push("Failed to create json array");
+        return NULL;
+    }
+
+    for (int i = 0; i < celix_arrayList_size(entry->typed.arrayValue); ++i) {
+        celix_array_list_entry_t arrayEntry = 
celix_arrayList_getEntry(entry->typed.arrayValue, i);
+        celix_array_list_element_type_t elType = 
celix_arrayList_getElementType(entry->typed.arrayValue);
+        json_t* jsonValue = 
celix_properties_arrayElementEntryValueToJson(elType, arrayEntry);
+        if (!jsonValue) {
+            celix_err_push("Failed to create json string");
+            json_decref(array);
+            return NULL;
+        }
+        int rc = json_array_append_new(array, jsonValue);
+        if (rc != 0) {
+            celix_err_push("Failed to append json string to array");
+            json_decref(array);
+            return NULL;
+        }
+    }
+    return array;
+}
+
+static json_t* celix_properties_entryValueToJson(const 
celix_properties_entry_t* entry) {
+    switch (entry->valueType) {
+        case CELIX_PROPERTIES_VALUE_TYPE_STRING:
+            return json_string(entry->value);
+        case CELIX_PROPERTIES_VALUE_TYPE_LONG:
+            return json_integer(entry->typed.longValue);
+        case CELIX_PROPERTIES_VALUE_TYPE_DOUBLE:
+            return json_real(entry->typed.doubleValue);
+        case CELIX_PROPERTIES_VALUE_TYPE_BOOL:
+            return json_boolean(entry->typed.boolValue);
+        case CELIX_PROPERTIES_VALUE_TYPE_VERSION:
+            return celix_properties_versionToJson(entry->typed.versionValue);
+        case CELIX_PROPERTIES_VALUE_TYPE_ARRAY_LIST:
+            return celix_properties_arrayEntryValueToJson(entry);
+        default:
+            //LCOV_EXCL_START
+            celix_err_pushf("Unexpected properties entry type %d", 
entry->valueType);\
+            return NULL;
+            //LCOV_EXCL_STOP
+    }
+}
+
 celix_status_t celix_properties_saveToStream(const celix_properties_t* 
properties, FILE* stream) {
-    celix_status_t status = CELIX_SUCCESS;
-    (void)properties;
-    fprintf(stream, "{}");
-    return status;
+    json_t* root = json_object();
+    if (!root) {
+        celix_err_push("Failed to create json object");
+    }
+
+    CELIX_PROPERTIES_ITERATE(properties, iter) {
+        const char* key = iter.key;
+        json_t* value = celix_properties_entryValueToJson(&iter.entry);
+        if (!value) {
+            json_decref(root);
+            return CELIX_ENOMEM; //TODO improve error
+        }
+        int rc = json_object_set_new(root, key, value);
+        if (rc != 0) {
+            celix_err_push("Failed to set json object");
+            json_decref(root);
+            return CELIX_ENOMEM; //TODO improve error
+        }
+    }
+
+    int rc = json_dumpf(root, stream, JSON_COMPACT); //TODO make celix 
properties flags for COMPACT and INDENT and maybe other json flags
+    json_decref(root);
+    if (rc != 0) {
+        celix_err_push("Failed to dump json object");
+        return CELIX_ENOMEM; //TODO improve error
+    }
+    return CELIX_SUCCESS;
 }
 
 celix_status_t celix_properties_loadFromStream(FILE* stream, 
celix_properties_t** out) {

Reply via email to