diff --git a/src/brayns/core/json/types/Objects.h b/src/brayns/core/json/types/Objects.h index dbc8fe3d1..cde5b5317 100644 --- a/src/brayns/core/json/types/Objects.h +++ b/src/brayns/core/json/types/Objects.h @@ -49,6 +49,43 @@ struct JsonObjectInfo std::vector> fields; }; +template +struct JsonObjectReflector; + +template +concept ReflectedJsonObject = requires { + { JsonObjectReflector::reflect() } -> std::same_as>; +}; + +template +const JsonObjectInfo &reflectJsonObject() +{ + static const auto info = JsonObjectReflector::reflect(); + return info; +} + +template +struct JsonReflector +{ + static JsonSchema getSchema() + { + const auto &info = reflectJsonObject(); + return getJsonObjectSchema(info); + } + + static void serialize(const T &value, JsonValue &json) + { + const auto &info = reflectJsonObject(); + serializeJsonObject(info, value, json); + } + + static void deserialize(const JsonValue &json, T &value) + { + const auto &info = reflectJsonObject(); + deserializeJsonObject(info, json, value); + } +}; + template JsonSchema getJsonObjectSchema(const JsonObjectInfo &info) { @@ -106,51 +143,88 @@ void deserializeJsonObject(const JsonObjectInfo &info, const JsonValue &json, } } -inline void removeJsonObjectDefaults(std::map &properties) +template +concept PtrToReflectedJson = std::is_pointer_v && ReflectedJson>>; + +template +concept PtrToMutableReflectedJson = PtrToReflectedJson && !std::is_const_v>; + +template +concept JsonFieldGetter = requires(T getter, Parent &object) { + { getter(std::as_const(object)) } -> PtrToReflectedJson; + { getter(object) } -> PtrToMutableReflectedJson; +}; + +template +JsonField createJsonField(std::string name, JsonFieldGetter auto getFieldPtr) { - for (auto &[key, field] : properties) + using Child = std::decay_t()))>; + + auto serializeField = [=](const auto &object, auto &json) { - field.defaultValue = std::nullopt; - removeJsonObjectDefaults(field.properties); - } + const auto &value = *getFieldPtr(object); + serializeToJson(value, json); + }; + + auto deserializeField = [=](const auto &json, auto &object) + { + auto &value = *getFieldPtr(object); + deserializeJson(json, value); + }; + + return { + .name = std::move(name), + .schema = getJsonSchema(), + .serialize = std::move(serializeField), + .deserialize = std::move(deserializeField), + }; } template -struct JsonObjectReflector; +concept PtrToReflectedJsonObject = std::is_pointer_v && ReflectedJsonObject>>; template -concept ReflectedJsonObject = requires { - { JsonObjectReflector::reflect() } -> std::same_as>; -}; +concept PtrToMutableReflectedJsonObject = PtrToReflectedJsonObject && !std::is_const_v>; -template -const JsonObjectInfo &reflectJsonObject() -{ - static const auto info = JsonObjectReflector::reflect(); - return info; -} +template +concept JsonChildGetter = requires(T callable, Parent parent) { + { callable(std::as_const(parent)) } -> PtrToReflectedJsonObject; + { callable(parent) } -> PtrToMutableReflectedJsonObject; +}; -template -struct JsonReflector +template +JsonField convertJsonField(const JsonField &child, JsonChildGetter auto getChildPtr) { - static JsonSchema getSchema() + auto childSerialize = child.serialize; + auto parentSerialize = [=](const auto &object, auto &json) { - const auto &info = reflectJsonObject(); - return getJsonObjectSchema(info); - } + const auto &childObject = *getChildPtr(object); + childSerialize(childObject, json); + }; - static void serialize(const T &value, JsonValue &json) + auto childDeserialize = child.deserialize; + auto parentDeserialize = [=](const auto &json, auto &object) { - const auto &info = reflectJsonObject(); - serializeJsonObject(info, value, json); - } + auto &childObject = *getChildPtr(object); + childDeserialize(json, childObject); + }; - static void deserialize(const JsonValue &json, T &value) + return { + .name = child.name, + .schema = child.schema, + .serialize = std::move(parentSerialize), + .deserialize = std::move(parentDeserialize), + }; +} + +inline void removeJsonObjectDefaults(std::map &properties) +{ + for (auto &[key, field] : properties) { - const auto &info = reflectJsonObject(); - deserializeJsonObject(info, json, value); + field.defaultValue = std::nullopt; + removeJsonObjectDefaults(field.properties); } -}; +} class JsonFieldBuilder { @@ -225,18 +299,6 @@ class JsonFieldBuilder JsonSchema *_schema; }; -template -concept PtrToReflectedJson = std::is_pointer_v && ReflectedJson>>; - -template -concept PtrToMutableReflectedJson = PtrToReflectedJson && !std::is_const_v>; - -template -concept JsonFieldGetter = requires(T getter, Parent &object) { - { getter(std::as_const(object)) } -> PtrToReflectedJson; - { getter(object) } -> PtrToMutableReflectedJson; -}; - template class JsonBuilder { @@ -248,28 +310,22 @@ class JsonBuilder JsonFieldBuilder field(std::string name, JsonFieldGetter auto getFieldPtr) { - using FieldType = std::decay_t()))>; - - auto serializeField = [=](const auto &object, auto &json) - { - const auto &value = *getFieldPtr(object); - serializeToJson(value, json); - }; + auto field = createJsonField(std::move(name), std::move(getFieldPtr)); + _info.fields.push_back(std::move(field)); + return JsonFieldBuilder(_info.fields.back().schema); + } - auto deserializeField = [=](const auto &json, auto &object) - { - auto &value = *getFieldPtr(object); - deserializeJson(json, value); - }; + void extend(JsonChildGetter auto getChildPtr) + { + using Child = std::decay_t()))>; - _info.fields.push_back({ - .name = std::move(name), - .schema = getJsonSchema(), - .serialize = std::move(serializeField), - .deserialize = std::move(deserializeField), - }); + const auto &child = reflectJsonObject(); - return JsonFieldBuilder(_info.fields.back().schema); + for (const auto &field : child.fields) + { + auto parentField = convertJsonField(field, std::move(getChildPtr)); + _info.fields.push_back(std::move(parentField)); + } } void removeDefaultValues() diff --git a/tests/core/json/TestJsonReflection.cpp b/tests/core/json/TestJsonReflection.cpp index 2999949cd..fde6dad45 100644 --- a/tests/core/json/TestJsonReflection.cpp +++ b/tests/core/json/TestJsonReflection.cpp @@ -108,6 +108,24 @@ struct JsonObjectReflector return builder.build(); } }; + +struct Extended +{ + Updatable child; + std::string additional; +}; + +template<> +struct JsonObjectReflector +{ + static auto reflect() + { + auto builder = JsonBuilder(); + builder.extend([](auto &object) { return &object.child; }); + builder.field("additional", [](auto &object) { return &object.additional; }); + return builder.build(); + } +}; } constexpr auto i32Min = std::numeric_limits::lowest(); @@ -376,3 +394,10 @@ TEST_CASE("Empty field") CHECK_EQ(object.value, 1); } + +TEST_CASE("Extension") +{ + auto schema = getJsonSchema(); + + CHECK_EQ(schema.properties.size(), 2); +}