getml / reflect-cpp

A C++20 library for fast serialization, deserialization and validation using reflection. Supports JSON, BSON, CBOR, flexbuffers, msgpack, TOML, XML, YAML / msgpack.org[C++20]
https://getml.github.io/reflect-cpp/
MIT License
982 stars 85 forks source link

Generic json #96

Closed laurentcau closed 3 months ago

laurentcau commented 5 months ago

Hi, I thought it was possible to use a generic way to load json (like a DOM) with your library. But it doesn't seem to be as simple as I thought. I tried something like that:

struct Value: public std::variant<bool, double, std::string> {};
struct GenericJSon : public std::map<std::string, std::variant<Value, std::vector<Value>, GenericJSon>> {};

std::string json_string(
"\
    {\
        \"toto\":\"titi\" \
        , \"float\":2.0 \
        , \"int\":2 \
        , \"intV\":[1,2,3]\
        , \"floatV\": [1.1, 2.2, 3.3] \
        , \"sturct\": {\"a\":1.1, \"b\":2.2} \
        , \"sturctV\": [{\"a\":1.1, \"b\":2.2}, {\"c\":true, \"d\":\"tutu\"}] \
        , \"sturctNestedV\": [{\"a\":1.1, \"b\":[{\"a\":1.1}, {\"b\":1.1}]}, {\"c\":true, \"d\":\"tutu\"}] \
    }\
"
        );
    auto generic = rfl::json::read<GenericJSon>(json_string).value();

It partially works without recursive definition (GenericJSon) but it fails to compile with it. Do you know how to fix this or do you have other idea to achieve this?

liuzicheng1987 commented 4 months ago

@laurentcau , you are on the right track.

But that won't compile because of restrictions C++ imposes on you...

struct GenericJSon : public std::map<std::string, std::variant<Value, std::vector<Value>, GenericJSon>>

A struct cannot inherit from itself. However, a struct can contain a pointer to itself:

https://github.com/getml/reflect-cpp/blob/main/docs/rfl_ref.md

And then you could define a ReflectionType, like this:

https://github.com/getml/reflect-cpp/blob/main/docs/custom_classes.md

I have actually been thinking about whether we should support something like this out-of-the-box, but so far it really hasn't been a top priority. But now that you have asked for it, I will think about it.

laurentcau commented 4 months ago
struct GenericJSon : public std::map<std::string, std::variant<Value, std::vector<Value>, GenericJSon>>

is valid c++. I have no problem to define and instantiate such object on VS2022. The issue is more in the rfl::json::read call that tries to count the number of fields that is infinite:

1>E:\build\unreal\external\include\rfl\internal\num_fields.hpp(145,24): error C2607: static assertion failed
1>(compiling source file '../../../../repos/eon/UnrealSandbox/Public/SDK/Core/Json/JsonTest.cpp')
1>E:\build\unreal\external\include\rfl\internal\num_fields.hpp(150,62):
1>while evaluating consteval function 'rfl::internal::CountFieldsHelper<T>::get_nested_base_field_count'
1>        with
1>        [
1>            T=GenericJSon
1>        ]
1>E:\build\unreal\external\include\rfl\internal\num_fields.hpp(150,62):
1>while evaluating consteval function 'rfl::internal::CountFieldsHelper<T>::get_nested_base_field_count'
1>        with
1>        [
1>            T=GenericJSon
1>        ]
1>E:\build\unreal\external\include\rfl\internal\num_fields.hpp(150,62):
....
liuzicheng1987 commented 4 months ago

@laurentcau , here is an untested first draft:

#ifndef RFL_GENERIC_HPP_
#define RFL_GENERIC_HPP_

#include <map>
#include <string>
#include <variant>
#include <vector>

#include "rfl.hpp"

class Generic {
 public:
  using ReflectionType = std::variant<bool, double, int, std::string,
                                      std::vector<rfl::Ref<Generic>>,
                                      std::map<std::string, rfl::Ref<Generic>>>;

  Generic(const ReflectionType& _value) : value_(_value) {}

  ~Generic() = default;

  ReflectionType reflection() const { return value_; };

 private:
  ReflectionType value_;
};

#endif

Feel free to improve it and then open a PR with this as a new library feature.

laurentcau commented 4 months ago

@liuzicheng1987 It works well. A small improvement: the extra rfl::Ref is in fact not needed. It works well without (and remove extra pointer dereference when you want to retrieve the data). (We can also use unordered_map.) So

    using ReflectionType = std::variant<bool, int, double, std::string,
        std::vector<Generic>,
        std::unordered_map<std::string, Generic>>;

is working fine on my side. Thanks!

laurentcau commented 4 months ago

also, you need to declare "int" before "double" to make "int" detection work.

liuzicheng1987 commented 4 months ago

@laurentcau, these are both good points.

This is just a first draft that I hacked into my keyboard within a minute or so.

Again, if you want to turn this into something more user-friendly and add it as an actual library feature, I would love to have it! Absolutely feel free to open a PR.