[c++] C++ JSON Serialization

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?

This question is related to c++ json

The answer is


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.


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!" }

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:


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);

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.


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 ajustements.

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 mentionned 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


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.


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
        }
    ]
}

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.