69

I want a way to serialize and deserialize Objects to JSON, as automatic as possible.

Serialize: For me, the ideal way is that if I call in an instance JSONSerialize() it returns an string with a JSON object that has all the public properties of the object as "name_of_property": "value". For those values that are primitives, it is straightforward, for objects it should try to call on each JSONSerialize() or ToString() or something like that to recursively serialize all the public properties. For collections it should also behave correctly (just vectors/arrays will be ok).

Deserialize: Just make an instance of the given object (let's say a dog) and call JSONDeserialize(json_string), and that should fill all the public properties, creating the needed objects in case that the properties are not primitives, or the needed collections.

An example should run like that:

Dog *d1 = new Dog();
d1->name = "myDog";

string serialized = d1->JSONSerialize();

Dog *d2 = new Dog();
d2->JSONDeserialize(serialized);
std::cout << d2->name; // This will print "myDog"

Or like that:

Dog *d1 = new Dog();
d1->name = "myDog";

string serialized = JSONSerializer.Serialize(d1);

Dog *d2 = JSONSerializer.Deserialize(serialized, Dog);
std::cout << d2->name; // This will print "myDog"

How can I pull this off easily?

4444
  • 3,541
  • 10
  • 32
  • 43
Vicenç Gascó
  • 1,326
  • 2
  • 13
  • 21
  • Possible duplicate of: http://stackoverflow.com/questions/8220130/converting-c-class-to-json – rhughes Jul 09 '13 at 13:50
  • @rhughes: I've took a look at that post... but I don't find there what I am looking for... does it means that there's no way to do what I am asking for? – Vicenç Gascó Jul 09 '13 at 14:13
  • 2
    Not directly at least. What you are asking for requires reflection, the ability to enumerate and evaluate properties of a type at runtime. C++ can not do that directly. You might be able to find a library, but afaik there is no way to do this without you at some point explicitly specifying the available fields plus their names and types. – Wutz Jul 09 '13 at 14:19
  • Do you know if there is something like that in Java? The project is being evaluated and I just want to know all the options! – Vicenç Gascó Jul 09 '13 at 15:04
  • @VicençGascó: "Something like that" should be available in python(although I strongly dislike it, working with json in python is easy), lisp and similar languages. If language supports structures and has "interpreter" where you can type and execute commands immediately, then this feature may be available. – SigTerm Jul 09 '13 at 16:37
  • possible duplicate of [Is there a C++ library to read JSON documents into C++ objects?](http://stackoverflow.com/questions/6538725/is-there-a-c-library-to-read-json-documents-into-c-objects) – Sebastian Nov 14 '13 at 09:50

10 Answers10

114

There is no reflection in C++. True. But if the compiler can't provide you the metadata you need, you can provide it yourself.

Let's start by making a property struct:

template<typename Class, typename T>
struct PropertyImpl {
    constexpr PropertyImpl(T Class::*aMember, const char* aName) : member{aMember}, name{aName} {}

    using Type = T;

    T Class::*member;
    const char* name;
};

template<typename Class, typename T>
constexpr auto property(T Class::*member, const char* name) {
    return PropertyImpl<Class, T>{member, name};
}

Of course, you also can have a property that takes a setter and getter instead of a pointer to member, and maybe read only properties for calculated value you'd like to serialize. If you use C++17, you can extend it further to make a property that works with lambdas.

Ok, now we have the building block of our compile-time introspection system.

Now in your class Dog, add your metadata:

struct Dog {
    std::string barkType;
    std::string color;
    int weight = 0;
    
    bool operator==(const Dog& rhs) const {
        return std::tie(barkType, color, weight) == std::tie(rhs.barkType, rhs.color, rhs.weight);
    }
    
    constexpr static auto properties = std::make_tuple(
        property(&Dog::barkType, "barkType"),
        property(&Dog::color, "color"),
        property(&Dog::weight, "weight")
    );
};

We will need to iterate on that list. To iterate on a tuple, there are many ways, but my preferred one is this:

template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F&& f) {
    using unpack_t = int[];
    (void)unpack_t{(static_cast<void>(f(std::integral_constant<T, S>{})), 0)..., 0};
}

If C++17 fold expressions are available in your compiler, then for_sequence can be simplified to:

template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F&& f) {
    (static_cast<void>(f(std::integral_constant<T, S>{})), ...);
}

This will call a function for each constant in the integer sequence.

If this method don't work or gives trouble to your compiler, you can always use the array expansion trick.

Now that you have the desired metadata and tools, you can iterate through the properties to unserialize:

// unserialize function
template<typename T>
T fromJson(const Json::Value& data) {
    T object;

    // We first get the number of properties
    constexpr auto nbProperties = std::tuple_size<decltype(T::properties)>::value;
    
    // We iterate on the index sequence of size `nbProperties`
    for_sequence(std::make_index_sequence<nbProperties>{}, [&](auto i) {
        // get the property
        constexpr auto property = std::get<i>(T::properties);

        // get the type of the property
        using Type = typename decltype(property)::Type;

        // set the value to the member
        // you can also replace `asAny` by `fromJson` to recursively serialize
        object.*(property.member) = Json::asAny<Type>(data[property.name]);
    });

    return object;
}

And for serialize:

template<typename T>
Json::Value toJson(const T& object) {
    Json::Value data;

    // We first get the number of properties
    constexpr auto nbProperties = std::tuple_size<decltype(T::properties)>::value;
    
    // We iterate on the index sequence of size `nbProperties`
    for_sequence(std::make_index_sequence<nbProperties>{}, [&](auto i) {
        // get the property
        constexpr auto property = std::get<i>(T::properties);

        // set the value to the member
        data[property.name] = object.*(property.member);
    });

    return data;
}

If you want recursive serialization and unserialization, you can replace asAny by fromJson.

Now you can use your functions like this:

Dog dog;

dog.color = "green";
dog.barkType = "whaf";
dog.weight = 30;

Json::Value jsonDog = toJson(dog); // produces {"color":"green", "barkType":"whaf", "weight": 30}
auto dog2 = fromJson<Dog>(jsonDog);

std::cout << std::boolalpha << (dog == dog2) << std::endl; // pass the test, both dog are equal!

Done! No need for run-time reflection, just some C++14 goodness!

This code could benefit from some improvement, and could of course work with C++11 with some adjustments.

Note that one would need to write the asAny function. It's just a function that takes a Json::Value and call the right as... function, or another fromJson.

Here's a complete, working example made from the various code snippet of this answer. Feel free to use it.

As mentioned in the comments, this code won't work with msvc. Please refer to this question if you want a compatible code: Pointer to member: works in GCC but not in VS2015

Pang
  • 9,564
  • 146
  • 81
  • 122
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • Turns out this doesn't work after all, you cannot have the address of an instance member at compile time.. – Michael Aug 22 '16 at 16:38
  • Pointer to members are known at compile time. You can use them as template parameter. However, there is no such thing as an "instance member pointer". Pointer to member are not related to any instance, they must be used with an instance in order to work. – Guillaume Racicot Aug 22 '16 at 16:42
  • I did not had a compiler with me at the time. Would you care if I ensure that the code is correct? If yes, I would be glad to improve this post. – Guillaume Racicot Aug 22 '16 at 16:43
  • Yes of course! Isn't only the "offset" of the member known at compile time? – Michael Aug 22 '16 at 16:54
  • If pointer to members are known at compile time, the following should work right? `class User { public: int m_id; static constexpr int User::* m_pointer_to_id = &User::m_id; }` I get the error: C2131 "expression did not evaluate to a constant" in Visual Studio 2015. Maybe I'm missing something – Michael Aug 22 '16 at 17:08
  • I updated the answer with corrected code and added a link to a working example. Yes, pointer to member should be known at compile time and work, but maybe there's a small error somewhere and the compiler give you wrong diagnostic. I cannot be sure for visual studio, maybe you will be luckier with the updated code. If you find something that should work but don't build on visual studio, consider reporting a bug. – Guillaume Racicot Aug 22 '16 at 17:29
  • Thank you for the updated code. Unfortunately it doesn't compile in Visual Studio so indeed it's probably a bug :( – Michael Aug 22 '16 at 18:02
  • Try to update MSVC. The 2015 update 3 version is much better when mixing pointer to member and constexpr. – Guillaume Racicot Aug 22 '16 at 18:11
  • Great work, can I advise you to use this json package ? [nlohmann/json](https://github.com/nlohmann/json). With some improvements I think you can have a solid library to use. – Folkvir Aug 23 '16 at 11:30
  • Great job. Thanks. I cannot compile the updated answer with `gcc 4.8.4` or `visual studio 2015 update 3`. Worked perfectly after using `gcc 4.9.x` – Frank Liu Oct 07 '16 at 02:24
  • @FrankLiu If you can use c++14, you might want to use `std::index_sequence` instead of c++11 style iteration. – Guillaume Racicot Oct 07 '16 at 02:43
  • @GuillaumeRacicot Thanks for the tip. Currently I can only using `std::string` or `int` type properties in my class. How to extend this code to support `std::vector` type of properties? – Frank Liu Oct 07 '16 at 04:39
  • @FrankLiu You can extend it through the `asAny` function in `doSetData`. As I mentioned, since you write this function, you can specialize it for any type you want. including vector or another type to unserialize. You can implement a similar function to transform any type to Json recursively in your data structure. – Guillaume Racicot Oct 07 '16 at 05:16
  • Hi! I'm not sure I understand this: object.*(property.member) = Json::asAny(data[property.name]); I understand that Json is probably another class, why is it that we can use data[property.name]? data is not an array though. – Nicholas Humphrey Jan 03 '17 at 04:43
  • 1
    @FanZhang I assumed that the json library used had an overloaded `operator[](std::string)` for `Json::Value`. If you look at the live example (the coliru link) at line 22, you'll see that my rudimentary json struct has one. – Guillaume Racicot Jan 03 '17 at 04:54
  • @GuillaumeRacicot The example helps a lot. In the example, is it that you assume that we only store int or std::string in json, and in struct ValueData we only have a std::string and an int number? My perception of the example is that in practice (de-serialization) we will read a json line and we will call doSetData(), in which we will call the [] operator to get the value of subObject. Then we will use one of the asAny() to return int or std::string. And it seems that the pointer to the member decides automatically which asAny() to use, according to the type of the member. – Nicholas Humphrey Jan 03 '17 at 16:27
  • @FanZhang yes you are right. But as I said, you can extend the `asAny` function to call `toJson` in case of a subobject, so you can recursively serialize your classes, including vectors and other containers of you want. – Guillaume Racicot Jan 03 '17 at 16:31
  • @GuillaumeRacicot Thump up, this helps a lot! Actually it's fine as I only have std::string and int in my XML files. Just need to apply the idea and get something similar plugged into my badly written parser... :P – Nicholas Humphrey Jan 03 '17 at 16:40
  • I also found if i put the struct `Dog` inside a namespace, say `animal`, the code wont compile. – Frank Liu Mar 15 '17 at 01:40
  • @FrankLiu I fixed it. Smelled like a GCC bug. I wasn't that good in metaprogramming at the time, so now I used a better way to iterate the tuple and it fixed the problem. The complete example on coliru is fixed too. – Guillaume Racicot Mar 15 '17 at 02:36
  • I managed to make something similar, but more complex, to work with VS2015, yes. You just have to work around bugs like the one I linked in my question. Once you work around compiler quirks it's quite possible. – Guillaume Racicot Apr 01 '17 at 14:56
  • 3
    This is the most brilliant solution I've ever seen. – Ethan F. Jun 22 '17 at 20:49
  • 4
    You could add a helper macro to avoid typos. `#define PROPERTY(CLASS, MEMBER) property(&CLASS::MEMBER, #MEMBER)` to avoid having to copy/paste the name of the member in code. Replace `property(&Dog::barkType, "barkType")` with `PROPERTY(Dog, barkType)` – cppguy Jul 06 '17 at 23:38
  • 2
    @cppguy yeah I could have done that. However, I find the syntax quite "alien", and most of the time I dealt with serialization, some of the names were serialized under a different name. – Guillaume Racicot Jul 06 '17 at 23:45
  • You sir, @GuillaumeRacicot, are a master. – J. Doe Aug 10 '18 at 05:26
  • @GuillaumeRacicot : Amazing work! I was curious if this would work for classes in similar fashion as structs? Also for Nested serialization, In addition to change in `fromjson`, I'd add `asAny` for template T, `operator=` in `value` and `typename` in `valuedata` correct? – akshay dhule Jan 28 '19 at 19:59
  • @akshaydhule yes since struct and classes are the same things it will work with classes. The pointer to data member will access the pointed-to member even if private. For the rest, I'm not sure what do you mean by `operator=` and `valuedata` – Guillaume Racicot Jan 28 '19 at 21:23
  • Hey @GuillaumeRacicot, Thanks a ton for quick turnaround! I'm trying to use the implementation for nested class serialization, The change in fromjson, from asAny to fromjson alone doesn't work. Adding another class with dog datatype gives `error: no match for 'operator=' (operand types are 'Json::Value' and 'const animal::Dog')` . – akshay dhule Jan 28 '19 at 21:30
  • @GuillaumeRacicot Also, If I transfer public vars to private, It throws `error: incomplete type 'animal::Dog' used in nested name specifier` in line 145, which seems to create circular dependency. – akshay dhule Jan 28 '19 at 22:40
  • @akshaydhule does that work for you? I put the members in a private section and it seem to work. http://coliru.stacked-crooked.com/a/a24567dd0ede9b15 – Guillaume Racicot Jan 28 '19 at 23:22
  • @GuillaumeRacicot Okay, the order of variable declaration caused to throw exception. Thanks! Do you believe it should work for nested class ex: `class pet{ private: Dog d;}` Curious if it's possible to handle multilevel structure. – akshay dhule Jan 28 '19 at 23:44
  • 1
    @akshaydhule In a code I did for a company yes it worked. The secret lies in recorsivity: eg. call `fromJson` for each values in the loop in `forJson`. It will recursively serialize/deserialize. – Guillaume Racicot Jan 29 '19 at 01:49
  • @akshaydhule In fact you could even support `std::vector` to be json arrays of serializable objects. – Guillaume Racicot Jan 29 '19 at 01:50
  • Do you have a version of the code for C++11? @GuillaumeRacicot – sayem siam Mar 12 '19 at 15:56
  • 1
    @sayemsiam making a C++11 version is quite trivial. Just roll your own version of `index_sequence`, then also replace each place I used return type deduction to make the return type explicit. – Guillaume Racicot Mar 12 '19 at 16:01
  • I am using my own version of integer_sequence (http://coliru.stacked-crooked.com/a/348d0addd0cc90e6). I have also changed the code accordingly (http://coliru.stacked-crooked.com/a/67138d596daf166e). However I get an error "parse.h:102:20: error: ‘i’ is not a constant expression". Can you please take a look? @GuillaumeRacicot – sayem siam Mar 12 '19 at 17:59
  • @sayemsiam okay I inspected the code and it's clear why it doen't work. `constexpr` parameter is not a thing in C++. The lambda with the `auto` worked because the conversion operator of an intergral constant is constexpr, but the type is different for all values. If you want to use C++11, you'll have to use the array expansion trick or roll your own templated function object. – Guillaume Racicot Mar 12 '19 at 18:22
  • @GuillaumeRacicot thanks for the quick response. I tried to use different array expansion trick. Can you please give an example code? – sayem siam Mar 12 '19 at 18:26
  • @sayemsiam I'm talking about the array expansion trick [I linked in my answer](https://stackoverflow.com/a/25683817/2104697). [Here's a working example of this trick](https://godbolt.org/z/5RXqPl). It makes `S` available as a compile time constant. – Guillaume Racicot Mar 12 '19 at 18:32
  • @GuillaumeRacicot, Thanks, I have decided to use c++14. – sayem siam Mar 14 '19 at 21:36
  • This is fantastic. Thanks for sharing. – Robinson Aug 01 '19 at 20:19
15

For that you need reflection in C/C++, which doesn't exist. You need to have some meta data describing the structure of your classes (members, inherited base classes). For the moment C/C++ compilers don't automatically provide that information in built binaries.

I had the same idea in mind, and I used GCC XML project to get this information. It outputs XML data describing class structures. I have built a project and I'm explaining some key points in this page :

Serialization is easy, but we have to deal with complex data structure implementations (std::string, std::map for example) that play with allocated buffers. Deserialization is more complex and you need to rebuild your object with all its members, plus references to vtables ... a painful implementation.

For example you can serialize like this:

    // Random class initialization
    com::class1* aObject = new com::class1();

    for (int i=0; i<10; i++){
            aObject->setData(i,i);
    }      

    aObject->pdata = new char[7];
    for (int i=0; i<7; i++){
            aObject->pdata[i] = 7-i;
    }
    // dictionary initialization
    cjson::dictionary aDict("./data/dictionary.xml");

    // json transformation
    std::string aJson = aDict.toJson<com::class1>(aObject);

    // print encoded class
    cout << aJson << std::endl ;

To deserialize data it works like this:

    // decode the object
    com::class1* aDecodedObject = aDict.fromJson<com::class1>(aJson);

    // modify data
    aDecodedObject->setData(4,22);

    // json transformation
    aJson = aDict.toJson<com::class1>(aDecodedObject);
   
    // print encoded class
    cout << aJson << std::endl ;

Ouptuts:

>:~/cjson$ ./main
{"_index":54,"_inner":  {"_ident":"test","pi":3.141593},"_name":"first","com::class0::_type":"type","com::class0::data":[0,1,2,3,4,5,6,7,8,9],"com::classb::_ref":"ref","com::classm1::_type":"typem1","com::classm1::pdata":[7,6,5,4,3,2,1]}
{"_index":54,"_inner":{"_ident":"test","pi":3.141593},"_name":"first","com::class0::_type":"type","com::class0::data":[0,1,2,3,22,5,6,7,8,9],"com::classb::_ref":"ref","com::classm1::_type":"typem1","com::classm1::pdata":[7,6,5,4,3,2,1]}
>:~/cjson$ 

Usually these implementations are compiler dependent (ABI Specification for example), and require external descriptions to work (GCCXML output), thus are not really easy to integrate to projects.

Gabby Paolucci
  • 887
  • 8
  • 23
JBV06
  • 304
  • 2
  • 5
9

Does anything, easy like that, exists?? THANKS :))

C++ does not store class member names in compiled code, and there's no way to discover (at runtime) which members (variables/methods) class contains. In other words, you cannot iterate through members of a struct. Because there's no such mechanism, you won't be able to automatically create "JSONserialize" for every object.

You can, however, use any json library to serialize objects, BUT you'll have to write serialization/deserialization code yourself for every class. Either that, or you'll have to create serializeable class similar to QVariantMap that'll be used instead of structs for all serializeable objects.

In other words, if you're okay with using specific type for all serializeable objects (or writing serialization routines yourself for every class), it can be done. However, if you want to automatically serialize every possible class, you should forget about it. If this feature is important to you, try another language.

SigTerm
  • 26,089
  • 6
  • 66
  • 115
8

Using quicktype, you can generate C++ serializers and deserializers from JSON sample data.

For example, given the sample JSON:

{
  "breed": "Boxer",
  "age": 5,
  "tail_length": 6.5
}

quicktype generates:

#include "json.hpp"

namespace quicktype {
    using nlohmann::json;

    struct Dog {
        int64_t age;
        std::string breed;
        double tail_length;
    };


    inline json get_untyped(const json &j, const char *property) {
        if (j.find(property) != j.end()) {
            return j.at(property).get<json>();
        }
        return json();
    }
}

namespace nlohmann {

    inline void from_json(const json& _j, struct quicktype::Dog& _x) {
        _x.age = _j.at("age").get<int64_t>();
        _x.breed = _j.at("breed").get<std::string>();
        _x.tail_length = _j.at("tail_length").get<double>();
    }

    inline void to_json(json& _j, const struct quicktype::Dog& _x) {
        _j = json{{"age", _x.age}, {"breed", _x.breed}, {"tail_length", _x.tail_length}};
    }
}

To parse the Dog JSON data, include the code above, install Boost and json.hpp, then do:

Dog dog = nlohmann::json::parse(jsonString);
David Siegel
  • 1,604
  • 11
  • 13
  • Since I have already concluded that boost and nlohmann's JSON library were well worth their weight, this solution was [perfect for me](https://bitpost.com/news/2018/the-ultimate-dry-always-generate-your-code-from-json-schema/). – moodboom Sep 08 '18 at 15:40
4

In case that anyone still has this need (I have), I have written a library myself to deal with this problem. See here. It isn't completely automatic in that you have to describe all the fields in your classes, but it is as close as what we can get as C++ lacks reflection.

Siyuan Ren
  • 7,573
  • 6
  • 47
  • 61
4

Try json_dto. It is header-only and easy to use.

Simple example:

struct message_t
{
  std::string m_from;
  std::string m_text;
  
  // Entry point for json_dto.
  template < typename JSON_IO >
  void
  json_io( JSON_IO & io )
  {
    io
      & json_dto::mandatory( "from", m_from )
      & json_dto::mandatory( "text", m_text );
  }
};

This will be convertable to and from JSON:

{ "from" : "json_dto", "text" : "Hello world!" }
MaxV
  • 2,601
  • 3
  • 18
  • 25
kola
  • 310
  • 2
  • 10
3

The jsoncons C++ header-only library also supports conversion between JSON text and C++ objects. Decode and encode are defined for all C++ classes that have json_type_traits defined. The standard library containers are already supported, and json_type_traits can be specialized for user types in the jsoncons namespace.

Below is an example:

#include <iostream>
#include <jsoncons/json.hpp>

namespace ns {
    enum class hiking_experience {beginner,intermediate,advanced};

    class hiking_reputon
    {
        std::string rater_;
        hiking_experience assertion_;
        std::string rated_;
        double rating_;
    public:
        hiking_reputon(const std::string& rater,
                       hiking_experience assertion,
                       const std::string& rated,
                       double rating)
            : rater_(rater), assertion_(assertion), rated_(rated), rating_(rating)
        {
        }

        const std::string& rater() const {return rater_;}
        hiking_experience assertion() const {return assertion_;}
        const std::string& rated() const {return rated_;}
        double rating() const {return rating_;}
    };

    class hiking_reputation
    {
        std::string application_;
        std::vector<hiking_reputon> reputons_;
    public:
        hiking_reputation(const std::string& application, 
                          const std::vector<hiking_reputon>& reputons)
            : application_(application), 
              reputons_(reputons)
        {}

        const std::string& application() const { return application_;}
        const std::vector<hiking_reputon>& reputons() const { return reputons_;}
    };

} // namespace ns

// Declare the traits using convenience macros. Specify which data members need to be serialized.

JSONCONS_ENUM_TRAITS_DECL(ns::hiking_experience, beginner, intermediate, advanced)
JSONCONS_ALL_CTOR_GETTER_TRAITS(ns::hiking_reputon, rater, assertion, rated, rating)
JSONCONS_ALL_CTOR_GETTER_TRAITS(ns::hiking_reputation, application, reputons)

using namespace jsoncons; // for convenience

int main()
{

std::string data = R"(
    {
       "application": "hiking",
       "reputons": [
       {
           "rater": "HikingAsylum",
           "assertion": "advanced",
           "rated": "Marilyn C",
           "rating": 0.90
         }
       ]
    }
)";

    // Decode the string of data into a c++ structure
    ns::hiking_reputation v = decode_json<ns::hiking_reputation>(data);

    // Iterate over reputons array value
    std::cout << "(1)\n";
    for (const auto& item : v.reputons())
    {
        std::cout << item.rated() << ", " << item.rating() << "\n";
    }

    // Encode the c++ structure into a string
    std::string s;
    encode_json<ns::hiking_reputation>(v, s, indenting::indent);
    std::cout << "(2)\n";
    std::cout << s << "\n";
}    

Output:

(1)
Marilyn C, 0.9
(2)
{
    "application": "hiking",
    "reputons": [
        {
            "assertion": "advanced",
            "rated": "Marilyn C",
            "rater": "HikingAsylum",
            "rating": 0.9
        }
    ]
}
Daniel
  • 728
  • 7
  • 11
2

Using ThorsSerializer

Dog *d1 = new Dog();
d1->name = "myDog";

std::stringstream  stream << ThorsAnvil::Serialize::jsonExport(d1);
string serialized = stream.str();

Dog *d2 = nullptr;
stream >> ThorsAnvil::Serialize::jsonImport(d2);
std::cout << d2->name; // This will print "myDog"

I think that is pretty close to your original.
There is a tiny bit of set up. You need to declare your class is Serializable.

#include "ThorSerialize/Traits.h"
#include "ThorSerialize/JsonThor.h"

struct Dog
{
    std::string  name;
};

// Declare the "Dog" class is Serializable; Serialize the member "name"
ThorsAnvil_MakeTrait(Dog, name);

No other coding is required.

Complete Examples can be found:

Martin York
  • 257,169
  • 86
  • 333
  • 562
2

Not yet mentioned, though it was the first in my search result: https://github.com/nlohmann/json

Perks listed:

  • Intuitive syntax (looks great!)
  • Single header file to include, nothing else
  • Ridiculously tested

Also, it's under the MIT License.

I'll be honest: I have yet to use it, but through some experience I have a knack for determining when I come across a really well-made c++ library.

Andrew
  • 5,839
  • 1
  • 51
  • 72
1

This is my attempt using Qt: https://github.com/carlonluca/lqobjectserializer. A JSON like this:

{"menu": {
    "header": "SVG Viewer",
    "items": [
        {"id": "Open"},
        {"id": "OpenNew", "label": "Open New"},
        null,
        {"id": "ZoomIn", "label": "Zoom In"},
        {"id": "ZoomOut", "label": "Zoom Out"},
        {"id": "OriginalView", "label": "Original View"},
        null,
        {"id": "Quality"},
        {"id": "Pause"},
        {"id": "Mute"},
        null,
        {"id": "Find", "label": "Find..."},
        {"id": "FindAgain", "label": "Find Again"},
        {"id": "Copy"},
        {"id": "CopyAgain", "label": "Copy Again"},
        {"id": "CopySVG", "label": "Copy SVG"},
        {"id": "ViewSVG", "label": "View SVG"},
        {"id": "ViewSource", "label": "View Source"},
        {"id": "SaveAs", "label": "Save As"},
        null,
        {"id": "Help"},
        {"id": "About", "label": "About Adobe CVG Viewer..."}
    ]
}}

can be deserialized by declaring classes like these:

L_BEGIN_CLASS(Item)
L_RW_PROP(QString, id, setId, QString())
L_RW_PROP(QString, label, setLabel, QString())
L_END_CLASS

L_BEGIN_CLASS(Menu)
L_RW_PROP(QString, header, setHeader)
L_RW_PROP_ARRAY_WITH_ADDER(Item*, items, setItems)
L_END_CLASS

L_BEGIN_CLASS(MenuRoot)
L_RW_PROP(Menu*, menu, setMenu, nullptr)
L_END_CLASS

and writing writing:

LDeserializer<MenuRoot> deserializer;
QScopedPointer<MenuRoot> g(deserializer.deserialize(jsonString));

You also need to inject mappings for meta objects once:

QHash<QString, QMetaObject> factory {
    { QSL("Item*"), Item::staticMetaObject },
    { QSL("Menu*"), Menu::staticMetaObject }
};

I'm looking for a way to avoid this.

Luca Carlon
  • 9,546
  • 13
  • 59
  • 91