C++ convert json to object

16,905

Solution 1

There is two solutions.

Do it manually

you can provide a function that takes a json::value and return the object of your type:

User fromJson(json::value data) {
    return User{data[U("username")].as_string(), data[U("info")].as_string()};
}

Do it automatically

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

Let's start by making a propery struct:

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

    using Type = T;

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

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

Now in your class user, add your metadata:

struct User {
    constexpr static auto properties = std::make_tuple(
        Property<User, std::string>{&User::username, "username"},
        Property<User, std::string>{&User::info, "info"}
    );

private:
    std::string username;
    std::string info;
};

Now that you have the desired metadata, you can iterate through it by recursion:

template<std::size_t iteration, typename T>
void doSetData(T&& object, const json::value& data) {
    // get the property
    constexpr auto property = std::get<iteration>(std::decay_t<T>::properties);

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

    // set the value to the member
    object.*(property.member) = asAny<Type>(data[U(property.name)]);
}

template<std::size_t iteration, typename T, typename = std::enable_if_t<(iteration > 0)>>
void setData(T&& object, const json::value& data) {
    doSetData<iteration>(object, data);
    // next iteration
    setData<iteration - 1>(object, data);
}

template<std::size_t iteration, typename T, typename = std::enable_if_t<(iteration == 0)>>
void setData(T&& object, const json::value& data) {
    doSetData<iteration>(object, data);
}

template<typename T>
T fromJson(Json::Value data) {
    T object;

    setData<std::tuple_size<decltype(T::properties)>::value - 1>(object, data);

    return object;
}

That will do the trick.

I did not test this code, so if you have trouble, tell me in the comments.

Note that you will 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 ;)

Solution 2

I have reworked the Guillaume solution to support c++11. A full working solution with some "polyfill" of c++14's decay_t and enable_if_t features to work with c++11 is below:

// main.cpp
#include <iostream>
#include <type_traits>
#include <tuple>
#include <jsoncpp/json/json.h>

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

    using Type = T;

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

class User
{
    std::string username;
    std::string info;
public:
    constexpr static auto properties = std::make_tuple(Property<User, std::string>{&User::username, "username"},
                                                       Property<User, std::string>{&User::info, "info"});

    const std::string &getUsername() const
    {
        return username;
    }

    void setUsername(const std::string &username)
    {
        User::username = username;
    }

    const std::string &getInfo() const
    {
        return info;
    }

    void setInfo(const std::string &info)
    {
        User::info = info;
    }
};

template< class T >
using decay_t = typename std::decay<T>::type;

template< bool B, class T = void >
using enable_if_t = typename std::enable_if<B,T>::type;

template<std::size_t iteration, typename T>
void doSetData(T &&object, const Json::Value &data)
{
    constexpr auto property = std::get<iteration>(decay_t<T>::properties);
    using Type = typename decltype(property)::Type;
    object.*(property.member) = data[property.name].asString();
}

template<std::size_t iteration, typename T, enable_if_t<(iteration > 0)>* = nullptr>
void setData(T &&object, const Json::Value &data)
{
    doSetData<iteration>(object, data);
    setData<iteration - 1>(object, data);
}

template<std::size_t iteration, typename T, enable_if_t<(iteration == 0)>* = nullptr>
void setData(T &&object, const Json::Value &data)
{
    doSetData<iteration>(object, data);
}

template<typename T>
T fromJson(Json::Value data)
{
    T object;

    setData<std::tuple_size<decltype(T::properties)>::value - 1>(object, data);

    return object;
}

int main()
{
    Json::Value value;
    value["username"] = "fiorentinoing";
    value["info"] = "https://www.linkedin.com/in/fiorentinoing/";
    User u = fromJson<User>(value);
    std::cout << "Hello, "<< u.getUsername() <<"!" << std::endl;
    std::cout << "Please, visit "<< u.getInfo() <<"." << std::endl;
    return 0;
}

With libjsoncpp-dev as dependency, in order to build under Ubuntu 18.04 you can issue:

g++ --std=c++11 -o static_reflection main.cpp -ljsoncpp
Share:
16,905
Piotrek
Author by

Piotrek

Updated on June 07, 2022

Comments

  • Piotrek
    Piotrek almost 2 years

    I'm downloading json from my server. Object that I'm sending from server is C# object and looks like this:

    public class User
    {
        public string UserName { get; set; }
        public string Info { get; set; }
    }
    

    Now, I have to get this data in my C++ app. I use this library for it.

    Object that I've got from server is of the type: web::json::value

    How can I get UserName from this web::json::value?