Xardaska Xardaska - 3 months ago 47
C++ Question

Is it possible to deserialize using Boost.Hana?

I'm getting started with Boost.Hana and was wondering if there is a way to deserialize back into a Struct that is known to Boost.Hana.
I know it's pretty simple to serialize such a Struct into a json string for example, but i did not find any information about the other way around.
Is it currently just not possible to deserialize data with Boost.Hana or am i missing something?

Thanks in advance

Answer

Hana is a metaprogramming library. It provides tools that can be used to build more complex functionality like serialization, but it does not provide such functionality itself. It's simply not the scope of that library. Also, regarding your particular use case; parsing is not an easy problem and other libraries like Boost.Spirit already try to solve it.

That being said, I sketched an example of using Hana to deserialize JSON. The result is neither efficient nor robust, but it should be enough to give you a glimpse at how Hana could be used to achieve something better. Solving this problem correctly would require implementing a parser combinator library à-la Boost.Spirit, which I won't do here. Here you go:

template <typename T>
  std::enable_if_t<std::is_same<T, int>::value,
T> from_json(std::istream& in) {
    T result;
    in >> result;
    return result;
}

template <typename T>
  std::enable_if_t<std::is_same<T, std::string>::value,
T> from_json(std::istream& in) {
    char quote;
    in >> quote;

    T result;
    char c;
    while (in.get(c) && c != '"') {
        result += c;
    }
    return result;
}


template <typename T>
  std::enable_if_t<hana::Struct<T>::value,
T> from_json(std::istream& in) {
    T result;
    char brace;
    in >> brace;

    hana::for_each(hana::keys(result), [&](auto key) {
        in.ignore(std::numeric_limits<std::streamsize>::max(), ':');
        auto& member = hana::at_key(result, key);
        using Member = std::remove_reference_t<decltype(member)>;
        member = from_json<Member>(in);
    });
    in >> brace;
    return result;
}

template <typename Xs>
  std::enable_if_t<hana::Sequence<Xs>::value,
Xs> from_json(std::istream& in) {
    Xs result;
    char bracket;
    in >> bracket;
    hana::length(result).times.with_index([&](auto i) {
        if (i != 0u) {
            char comma;
            in >> comma;
        }

        auto& element = hana::at(result, i);
        using Element = std::remove_reference_t<decltype(element)>;
        element = from_json<Element>(in);
    });
    in >> bracket;
    return result;
}

And then you can use it like

struct Car {
    BOOST_HANA_DEFINE_STRUCT(Car,
        (std::string, brand),
        (std::string, model)
    );
};

struct Person {
    BOOST_HANA_DEFINE_STRUCT(Person,
        (std::string, name),
        (std::string, last_name),
        (int, age)
    );
};

int main() {
    std::istringstream json(R"EOS(
        [
            {
                "name": "John",
                "last_name": "Doe",
                "age": 30
            },
            {
                "brand": "BMW",
                "model": "Z3"
            },
            {
                "brand": "Audi",
                "model": "A4"
            }
        ]
    )EOS");

    auto actual = from_json<hana::tuple<Person, Car, Car>>(json);

    auto expected = hana::make_tuple(Person{"John", "Doe", 30},
                                     Car{"BMW", "Z3"},
                                     Car{"Audi", "A4"});

    assert(actual == expected);
}

The full example is available here.