Skip to content

Commit

Permalink
Added JSON object extension.
Browse files Browse the repository at this point in the history
  • Loading branch information
Adrien4193 committed Sep 11, 2024
1 parent 22bc002 commit 7da285d
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 60 deletions.
176 changes: 116 additions & 60 deletions src/brayns/core/json/types/Objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,43 @@ struct JsonObjectInfo
std::vector<JsonField<T>> fields;
};

template<typename T>
struct JsonObjectReflector;

template<typename T>
concept ReflectedJsonObject = requires {
{ JsonObjectReflector<T>::reflect() } -> std::same_as<JsonObjectInfo<T>>;
};

template<ReflectedJsonObject T>
const JsonObjectInfo<T> &reflectJsonObject()
{
static const auto info = JsonObjectReflector<T>::reflect();
return info;
}

template<ReflectedJsonObject T>
struct JsonReflector<T>
{
static JsonSchema getSchema()
{
const auto &info = reflectJsonObject<T>();
return getJsonObjectSchema(info);
}

static void serialize(const T &value, JsonValue &json)
{
const auto &info = reflectJsonObject<T>();
serializeJsonObject(info, value, json);
}

static void deserialize(const JsonValue &json, T &value)
{
const auto &info = reflectJsonObject<T>();
deserializeJsonObject(info, json, value);
}
};

template<typename T>
JsonSchema getJsonObjectSchema(const JsonObjectInfo<T> &info)
{
Expand Down Expand Up @@ -106,51 +143,88 @@ void deserializeJsonObject(const JsonObjectInfo<T> &info, const JsonValue &json,
}
}

inline void removeJsonObjectDefaults(std::map<std::string, JsonSchema> &properties)
template<typename T>
concept PtrToReflectedJson = std::is_pointer_v<T> && ReflectedJson<std::decay_t<std::remove_pointer_t<T>>>;

template<typename T>
concept PtrToMutableReflectedJson = PtrToReflectedJson<T> && !std::is_const_v<std::remove_pointer_t<T>>;

template<typename T, typename Parent>
concept JsonFieldGetter = requires(T getter, Parent &object) {
{ getter(std::as_const(object)) } -> PtrToReflectedJson;
{ getter(object) } -> PtrToMutableReflectedJson;
};

template<typename Parent>
JsonField<Parent> createJsonField(std::string name, JsonFieldGetter<Parent> auto getFieldPtr)
{
for (auto &[key, field] : properties)
using Child = std::decay_t<decltype(*getFieldPtr(std::declval<Parent &>()))>;

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<Child>(),
.serialize = std::move(serializeField),
.deserialize = std::move(deserializeField),
};
}

template<typename T>
struct JsonObjectReflector;
concept PtrToReflectedJsonObject = std::is_pointer_v<T> && ReflectedJsonObject<std::decay_t<std::remove_pointer_t<T>>>;

template<typename T>
concept ReflectedJsonObject = requires {
{ JsonObjectReflector<T>::reflect() } -> std::same_as<JsonObjectInfo<T>>;
};
concept PtrToMutableReflectedJsonObject = PtrToReflectedJsonObject<T> && !std::is_const_v<std::remove_pointer_t<T>>;

template<ReflectedJsonObject T>
const JsonObjectInfo<T> &reflectJsonObject()
{
static const auto info = JsonObjectReflector<T>::reflect();
return info;
}
template<typename T, typename Parent>
concept JsonChildGetter = requires(T callable, Parent parent) {
{ callable(std::as_const(parent)) } -> PtrToReflectedJsonObject;
{ callable(parent) } -> PtrToMutableReflectedJsonObject;
};

template<ReflectedJsonObject T>
struct JsonReflector<T>
template<typename Parent, typename Child>
JsonField<Parent> convertJsonField(const JsonField<Child> &child, JsonChildGetter<Parent> auto getChildPtr)
{
static JsonSchema getSchema()
auto childSerialize = child.serialize;
auto parentSerialize = [=](const auto &object, auto &json)
{
const auto &info = reflectJsonObject<T>();
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<T>();
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<std::string, JsonSchema> &properties)
{
for (auto &[key, field] : properties)
{
const auto &info = reflectJsonObject<T>();
deserializeJsonObject(info, json, value);
field.defaultValue = std::nullopt;
removeJsonObjectDefaults(field.properties);
}
};
}

class JsonFieldBuilder
{
Expand Down Expand Up @@ -225,18 +299,6 @@ class JsonFieldBuilder
JsonSchema *_schema;
};

template<typename T>
concept PtrToReflectedJson = std::is_pointer_v<T> && ReflectedJson<std::remove_const_t<std::remove_pointer_t<T>>>;

template<typename T>
concept PtrToMutableReflectedJson = PtrToReflectedJson<T> && !std::is_const_v<std::remove_pointer_t<T>>;

template<typename T, typename Parent>
concept JsonFieldGetter = requires(T getter, Parent &object) {
{ getter(std::as_const(object)) } -> PtrToReflectedJson;
{ getter(object) } -> PtrToMutableReflectedJson;
};

template<typename T>
class JsonBuilder
{
Expand All @@ -248,28 +310,22 @@ class JsonBuilder

JsonFieldBuilder field(std::string name, JsonFieldGetter<T> auto getFieldPtr)
{
using FieldType = std::decay_t<decltype(*getFieldPtr(std::declval<const T &>()))>;

auto serializeField = [=](const auto &object, auto &json)
{
const auto &value = *getFieldPtr(object);
serializeToJson(value, json);
};
auto field = createJsonField<T>(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<T> auto getChildPtr)
{
using Child = std::decay_t<decltype(*getChildPtr(std::declval<T &>()))>;

_info.fields.push_back({
.name = std::move(name),
.schema = getJsonSchema<FieldType>(),
.serialize = std::move(serializeField),
.deserialize = std::move(deserializeField),
});
const auto &child = reflectJsonObject<Child>();

return JsonFieldBuilder(_info.fields.back().schema);
for (const auto &field : child.fields)
{
auto parentField = convertJsonField<T>(field, std::move(getChildPtr));
_info.fields.push_back(std::move(parentField));
}
}

void removeDefaultValues()
Expand Down
25 changes: 25 additions & 0 deletions tests/core/json/TestJsonReflection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,24 @@ struct JsonObjectReflector<Updatable>
return builder.build();
}
};

struct Extended
{
Updatable child;
std::string additional;
};

template<>
struct JsonObjectReflector<Extended>
{
static auto reflect()
{
auto builder = JsonBuilder<Extended>();
builder.extend([](auto &object) { return &object.child; });
builder.field("additional", [](auto &object) { return &object.additional; });
return builder.build();
}
};
}

constexpr auto i32Min = std::numeric_limits<int>::lowest();
Expand Down Expand Up @@ -376,3 +394,10 @@ TEST_CASE("Empty field")

CHECK_EQ(object.value, 1);
}

TEST_CASE("Extension")
{
auto schema = getJsonSchema<Extended>();

CHECK_EQ(schema.properties.size(), 2);
}

0 comments on commit 7da285d

Please sign in to comment.