Boost::Hana(とopenravejson)で構造体をシリアライズする

## はじめに

[OpenRAVE](https://github.com/rdiankov/openrave)はロボティクス用のライブラリですが、それ以外にもいくつか有用な機能を内包しています。
https://github.com/rdiankov/openrave/blob/master/include/openrave/openravejson.h はその一つで、シリアライズに特化した[RapidJSON](https://rapidjson.org/)のラッパー機能を備えています。
標準ではvector/set/map(やそれらの入れ子)に対応しています。

このようなことができます。

```cpp
#include <openrave/openravejson.h>
#include <iostream>
#include <vector>
using openravejson::SaveJsonValue;
using openravejson::LoadJsonValue;
int main(){
    std::vector<int> v{1,2,3};
    std::cout<<openravejson::GetJsonString(v)<<std::endl;
}

[1,2,3]
```

が、普通には構造体を扱うことはできません。メンバ名と同じkey/valueを持った辞書を用意するという方法がありますが、ジェネリックな解法はC++では難しい。今回はBoost::Hanaを用いてジェネリックに処理することができました。

```cpp
struct s_example{
    int a;
    double b;
    bool c;
};
struct s_example2{
    s_example x;
};

// 通常、上記構造体等は外部から与えられる。
// 型の入れ子を実現するためには、元の構造体を継承するわけにも行かないので、BOOST_HANA_DEFINE_STRUCTを使うのは難しいと思われる。

#include <openrave/openravejson.h>
#include <boost/hana.hpp>
#include <string>

BOOST_HANA_ADAPT_STRUCT(
    s_example,
    a,b,c
);
BOOST_HANA_ADAPT_STRUCT(
    s_example2,
    x
);

namespace openravejson {

template<typename T>
void SaveJsonValue(rapidjson::Value& v, const T& t, rapidjson::Document::AllocatorType& alloc){
    v.SetObject();
    rapidjson::Value name, value;
    boost::hana::for_each(boost::hana::accessors<typename std::remove_const<typename std::remove_reference<decltype(t)>::type>::type>(), [&](auto e) {
        SaveJsonValue(name, boost::hana::to<char const*>(boost::hana::first(e)), alloc);
        SaveJsonValue(value, boost::hana::second(e)(t), alloc);
        v.AddMember(name, value, alloc);
    });
}

template<typename T>
inline void LoadJsonValue(const rapidjson::Value& v, T& t){
    if(v.IsObject()){
        boost::hana::for_each(boost::hana::accessors<typename std::remove_const<typename std::remove_reference<decltype(t)>::type>::type>(), [&](auto e){
            const char* key = boost::hana::to<const char*>(boost::hana::first(e));
            auto it = v.FindMember(key);
            if(it != v.MemberEnd()){
                LoadJsonValue(it->value, boost::hana::second(e)(t));
            }else{
                throw OpenraveJSONException("Key " + std::string(key) + " not found in json", MJE_Failed);
            }
        });
    }else{
        throw OpenraveJSONException("Cannot convert json type " + GetJsonTypeName(v) + " to Map", MJE_Failed);
    }
}

}

#include <iostream>
#include <vector>
using openravejson::SaveJsonValue;
using openravejson::LoadJsonValue;
int main(){
    std::vector<int> v{1,2,3};
    std::cout<<openravejson::GetJsonString(v)<<std::endl;

    s_example2 st{{9,3.14,true}};
    std::cout<<openravejson::GetJsonString(st)<<std::endl;
    rapidjson::Document doc;
    openravejson::ParseJson(doc,"{\"x\":\n{\"a\":10,\"b\":2.71,\"c\":false}\n}\n");
    openravejson::LoadJsonValue(doc,st);
    std::cout<<openravejson::GetJsonString(st)<<std::endl;
}
```

## 実行

```
$ g++ -std=c++14 json.cpp
$ ./a.out 
[1,2,3]
{"x":{"a":9,"b":3.14,"c":true}}
{"x":{"a":10,"b":2.71,"c":false}}
```

## 注1

- OpenRAVEの大部分はLGPLですが、openravejsonはApache Licenseで利用できます。
- [cpp_inspect](https://qiita.com/cielavenir/items/95615185b5ca47d9111d)も型の入れ子を扱えますが、openravejsonとは一切独立です。
- is_standard_layoutが偽になるような型に対し実行した場合は調べていません。

## 注2

まあ、正直、Boost::Hanaの方が本記事の主体であることに間違いないです。