まえがき
C++で任意のデータをシリアライズするためのクラスの実装方法を紹介します。
今回は前回に引き続きテキスト形式でのシリアライズ処理の実装について説明します。
ソース一式はGitHubで公開しています。
前提条件
今回実装したソースは以下の環境でビルド、動作確認しています。
実装
テキスト形式シリアライズクラスでは、任意のデータ型をテキスト形式(XML or JSON)に変換して、変換されたテキスト(文字列)をバイト配列としてアーカイブに書き込む処理を行います。
ここでは、クラスの構成要素ごとに詳しく説明していきます。
ライブラリインクルード
テキスト形式へのシリアライズには、boostライブラリのproperty_tree機能を使用します。
そのため、下記のヘッダをインクルードします。
property_treeはヘッダオンリーのライブラリのため、ヘッダインクルードのみで使用可能です。
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/json_parser.hpp>
マクロ
シリアライズの形式をXMLとJSONで切り替えるためのマクロを定義します。
#define TEXT_SERIALIZE_MODE_XML (0)
#define TEXT_SERIALIZE_MODE_JSON (1)
#define TEXT_SERIALIZE_MODE (TEXT_SERIALIZE_MODE_XML)
ノード
property_treeの木構造を表現するためのboost::property_tree::ptreeを、分かりやすいようにNodeという名前でエイリアス定義します。
using Node = boost::property_tree::ptree;
コンストラクタ
シリアライズクラスは複数インスタンスを生成する必要がないため、シングルトンとして実装しています。
メンバ変数などはないため、コンストラクタはデフォルト定義しています。
public:
static TextSerialization& GetInstance()
{
static TextSerialization instance;
return instance;
}
private:
TextSerialization() = default;
~TextSerialization() = default;
public:
TextSerialization(const TextSerialization&) = delete;
TextSerialization(TextSerialization&&) = delete;
TextSerialization& operator=(const TextSerialization&) = delete;
TextSerialization& operator=(TextSerialization&&) = delete;
インタフェース
インタフェース関数として、入力データをシリアライズしてアーカイブに変換するSerialize()、アーカイブをデシリアライズして出力データに変換するDeserialize()を定義します。
template <typename T>
void Serialize(const T& in_data, const string_t& name, Archive& out_archive)
{
Node root_node{};
this->Serialize(in_data, name, root_node);
this->WriteText(root_node, out_archive);
}
template <typename T>
void Deserialize(const Archive& in_archive, const string_t& name, T& out_data)
{
Node root_node{};
this->ReadText(in_archive, root_node);
this->Deserialize(root_node, name, out_data);
}
Serialize()ではNode型のルートノードを定義し、指定されたデータ型に応じたシリアライズ(ツリーノードに要素を追加)を行うSerialize()を呼び出します。構造体型などの場合は各メンバに対してSerialize()を呼び出し、すべてのデータをシリアライズします。
その後、シリアライズしたツリーノードをテキスト形式(文字列)に変換し、アーカイブにセットします。
Deserialize()では、アーカイブからテキストデータ(文字列)を取得し、指定形式(XML/JSON)でパース処理を行いツリーノードに変換します。
その後、指定されたデータ型に応じたDeserialize()を呼び出してデシリアライズ(ツリーノードから要素を取得し、元のデータに復元)します。構造体型などの場合は各メンバに対してDeserialize()を呼び出し、すべてのメンバをデシリアライズします。
内部関数
プライベートメンバ関数としては、データ型に応じたSerialize()、Deserialize()やテキスト ⇔ アーカイブ変換処理を実装します。
データ型に応じた処理内容を詳しく説明していきます。
論理型・整数型
論理型・整数型では、そのままツリーノードに変換が可能なため、ツリーノードへの追加・取得を行う処理を実装します。
template<typename T, type_traits::concept_t<std::is_integral<T>::value> = nullptr>
void Serialize(const T& in_data, Node& out_node)
{
out_node.put_value(in_data);
}
template<typename T, type_traits::concept_t<std::is_integral<T>::value> = nullptr>
void Deserialize(const Node& in_node, T& out_data)
{
if (auto value = in_node.get_value_optional<T>())
{
out_data = value.get();
}
else
{
THROW_APP_EXCEPTION("Value is nothing");
}
}
Serialize()ではput_value()でツリーノードにそのままデータを追加します。
Deserialize()では、get_value_optional<T>()でツリーノードから値の取得を試み、取得に成功したら値を取り出します。
値の取得に失敗した場合は異常な状態となっているため例外を送出します。
浮動小数型の場合は、そのままシリアライズしてテキストに変換すると、一定の桁数で丸められてしまい、デシリアライズで復元した際に、完全に同じ値にならないケースがあります。
そのため、浮動小数型を16進数の文字列に変換してシリアライズ(テキストに変換)し、デシリアライズ時には16進数文字列から元の浮動小数型に復元するようにします。
template<typename T, type_traits::concept_t<std::is_floating_point<T>::value> = nullptr>
void Serialize(const T& in_data, Node& out_node)
{
string_t in_str{};
this->ToHexString(in_data, in_str);
out_node.put_value(in_str);
}
template<typename T, type_traits::concept_t<std::is_floating_point<T>::value> = nullptr>
void Deserialize(const Node& in_node, T& out_data)
{
string_t out_str{};
if (auto value = in_node.get_value_optional<string_t>())
{
out_str = value.get();
this->FromHexString(out_str, out_data);
}
else
{
THROW_APP_EXCEPTION("Value is nothing");
}
}
Serialize()ではToHexString()で16進数文字列に変換し、put_value()でツリーノードにデータを追加します。
Deserialize()では、get_value_optional<T>()でツリーノードから文字列(string)として値の取得を試み、取得に成功したら値を取り出し、FromHexString()で元の浮動小数型に復元しています。
値の取得に失敗した場合は異常な状態となっているため例外を送出します。
inline void ToHexString(const float32_t& in_data, string_t& out_data)
{
std::stringstream ss;
binary::FloatIntConverter<float32_t, uint32_t> converter;
uint32_t int_data = converter.ConvertToInt(in_data);
ss << std::showbase;
ss << std::setw(4);
ss << std::hex << int_data;
out_data = ss.str();
}
inline void ToHexString(const float64_t& in_data, string_t& out_data)
{
std::stringstream ss;
binary::FloatIntConverter<float64_t, uint64_t> converter;
uint64_t int_data = converter.ConvertToInt(in_data);
ss << std::showbase;
ss << std::setw(8);
ss << std::hex << int_data;
out_data = ss.str();
}
inline void FromHexString(const string_t& in_data, float32_t& out_data)
{
uint32_t int_data = std::stoul(in_data, nullptr, 16);
binary::FloatIntConverter<float32_t, uint32_t> converter;
out_data = converter.ConvertToFloat(int_data);
}
inline void FromHexString(const string_t& in_data, float64_t& out_data)
{
uint64_t int_data = std::stoull(in_data, nullptr, 16);
binary::FloatIntConverter<float64_t, uint64_t> converter;
out_data = converter.ConvertToFloat(int_data);
}
ToHexString()では、binary::FloatIntConverterのConvertToInt()で一旦元の浮動小数型と同じビットサイズの符号無整数型に変換し、std::stringstreamで16進数の文字列に変換しています。
FromHexString()では、16進数文字列をstd::stoul()、std::stoull()で元の浮動小数型と同じビットサイズの符号無整数型に変換し、binary::FloatIntConverterのConvertToFloat()で元の浮動小数型に復元しています。
binary::FloatIntConverterの詳細については、GitHubのソースをご参照ください。
列挙型
列挙型(特にenum class)では、ツリーノードに変換できないため、基底型に変換して処理を行うようにしています。
template<typename T, type_traits::concept_t<std::is_enum<T>::value> = nullptr>
void Serialize(const T& in_data, Node& out_node)
{
this->Serialize(type_traits::underlying_cast(in_data), out_node);
}
template<typename T, type_traits::concept_t<std::is_enum<T>::value> = nullptr>
void Deserialize(const Node& in_node, T& out_data)
{
type_traits::underlying_type_t<T> data{};
this->Deserialize(in_node, data);
out_data = static_cast<T>(data);
}
Serialize()では、type_traits::underlying_castを用いて基底型(デフォルトではint)に変換してSerialize()を呼び出しています。
Deserialize()では、type_traits::underlying_type_t<T>で基底型の一時変数を用意し、基底型に対するDeserialize()を呼び出してから元のデータ型にstatic_castで変換しています。
type_traits::underlying_castおよびtype_traits::underlying_type_t<T>の詳細についてはGitHubのソースをご参照ください。
STLコンテナ(std::array、std::vector、std::pair、std::mapなど)に対しては、子ノードを追加し、その子ノードに対して、さらにコンテナに格納されている各要素に対するシリアライズ/デシリアライズ処理を行います。
template <typename T, size_t N>
void Serialize(const std::array<T, N>& in_data, const string_t& name, Node& out_node)
{
auto& child_node = out_node.add(name, "");
for (const auto& item : in_data)
{
this->Serialize(item, "item", child_node);
}
}
template <typename T, size_t N>
void Deserialize(const Node& in_node, const string_t& name, std::array<T, N>& out_data)
{
auto& child_node = in_node.get_child(name);
size_t i = 0;
for (const auto& item_node_pair : child_node)
{
T item{};
this->Deserialize(item_node_pair.second, item);
out_data[i] = item;
i++;
}
}
template <typename T>
void Serialize(const std::vector<T>& in_data, const string_t& name, Node& out_node)
{
auto& child_node = out_node.add(name, "");
for (const auto& item : in_data)
{
this->Serialize(item, "item", child_node);
}
}
template <typename T>
void Deserialize(const Node& in_node, const string_t& name, std::vector<T>& out_data)
{
auto& child_node = in_node.get_child(name);
out_data.clear();
out_data.reserve(child_node.size());
for (const auto& item_node_pair : child_node)
{
T item{};
this->Deserialize(item_node_pair.second, item);
out_data.push_back(item);
}
}
template <typename T1, typename T2>
void Serialize(const std::pair<T1, T2>& in_data, Node& out_node)
{
this->Serialize(in_data.first, "first", out_node);
this->Serialize(in_data.second, "second", out_node);
}
template <typename T1, typename T2>
void Deserialize(const Node& in_node, std::pair<T1, T2>& out_data)
{
T1 first{};
this->Deserialize(in_node, "first", first);
T2 second{};
this->Deserialize(in_node, "second", second);
out_data = std::make_pair<T1, T2>(first, second);
}
template <typename TKey, typename TValue>
void Serialize(const std::map<TKey, TValue>& in_data, const string_t& name, Node& out_node)
{
auto& child_node = out_node.add(name, "");
for (const auto& item : in_data)
{
this->Serialize(item, "item", child_node);
}
}
template <typename TKey, typename TValue>
void Deserialize(const Node& in_node, const string_t& name, std::map<TKey, TValue>& out_data)
{
auto& child_node = in_node.get_child(name);
for (const auto& item_node_pair : child_node)
{
TKey key{};
this->Deserialize(item_node_pair.second, "first", key);
TValue value{};
this->Deserialize(item_node_pair.second, "second", value);
out_data.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(value));
}
}
例としてstd::arrayに対するシリアライズ/デシリアライズ処理について説明します。
Serialize()では、指定された名前(メンバ変数名)で子ノード(chile_node)を追加し、コンテナの各要素に対してシリアライズを行うSerialize()を呼び出し、子ノード(chile_node)の子供(item)としてノードを追加していきます。
Deserialize()では、指定された名前(メンバ変数名)で子ノード(chile_node)を取得し、その子ノード(chile_node)の子供(item)の要素数分Deserialize()を呼び出して各要素をデシリアライズします。
std::vectorやstd::mapも同様に実装しています。
std::pairはメンバのfirst、secondに対してそれぞれSerialize()、Deserialize()を行うようにしています。
文字列型
文字列型では、整数型と同様、そのままツリーノードに変換が可能なため、ツリーノードへの追加・取得を行う処理を実装します。
inline void Serialize(const string_t& in_data, Node& out_node)
{
out_node.put_value(in_data);
}
inline void Deserialize(const Node& in_node, string_t& out_data)
{
if (auto value = in_node.get_value_optional<string_t>())
{
out_data = value.get();
}
else
{
THROW_APP_EXCEPTION("Value is nothing");
}
}
クラス/構造体型
クラス/構造体型では、バイナリ形式の場合と同様、メンバ変数をタプル(std::tuple)型で列挙して各メンバに対してSerialize()、Deserialize()を呼び出しています。
template <typename TUPLE, size_t ...I>
void SerializeTupleImple(Node& out_node, TUPLE&& t, std::index_sequence<I...>)
{
using swallow = std::initializer_list<int>;
(void)swallow
{
(void(this->Serialize(std::get<1>(std::get<I>(t)), std::get<0>(std::get<I>(t)), out_node)), 0)...
};
}
template <typename TUPLE, size_t ...I>
void DeserializeTupleImple(const Node& in_node, TUPLE&& t, std::index_sequence<I...>)
{
using swallow = std::initializer_list<int>;
(void)swallow
{
(void(this->Deserialize(in_node, std::get<0>(std::get<I>(t)), std::get<1>(std::get<I>(t)))), 0)...
};
}
template <typename TUPLE>
void SerializeTuple(Node& out_node, TUPLE&& t)
{
this->SerializeTupleImple(out_node, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
}
template <typename TUPLE>
void DeserializeTuple(const Node& in_node, TUPLE&& t)
{
this->DeserializeTupleImple(in_node, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
}
template<class T>
void Serialize(const T& in_data, Node& out_node, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
auto tuple = type_traits::DataTypeTraits<T>::GetNamedMembersAsTuple(in_data);
this->SerializeTuple(out_node, tuple);
}
template<class T>
void Deserialize(const Node& in_node, T& out_data, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
auto tuple = type_traits::DataTypeTraits<T>::GetNamedMembersAsTuple(out_data);
this->DeserializeTuple(in_node, tuple);
}
Serialize()を例に説明します。
Serialize()では、type_traits::DataTypeTraits<T>::GetNamedMembersAsTuple()を呼び出すことで、入力データのメンバとそのメンバ名をタプル(std::tuple)型で取得し、SerializeTuple()に渡しています。
SerializeTuple()ではstd::forwardでタプルデータを渡し、またstd::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{}でタプルの要素数分のインデックスシーケンスを生成して渡しています。
さらにSerializeTupleImple()では、渡されたタプルと要素のインデックスにより指定インデックス位置のメンバ変数を取り出し、メンバの型に対応したSerialize()を呼び出しています。
タプル要素インデックス(I)は可変長テンプレートパラメータとなっており、SwallowイディオムおよびIndex Tupleイディオムによって順次展開されるため、すべてのタプルに含まれるすべてのメンバに対して順番にSerialize()が呼ばれる仕組みになっています。
type_traits::DataTypeTraits<T>::GetNamedMembersAsTuple()の定義方法は後ほど使い方の項で説明します。
名前付きオブジェクト
メンバ変数やコンテナ要素など同じ階層に複数のデータが含まれている場合には、名前を付けてどのデータかを特定できるようにする必要があります。
そういった名前付きオブジェクトのシリアライズ/デシリアライズを共通化しています。
template <typename T>
void Serialize(const T& in_data, const string_t& name, Node& out_node)
{
auto& child_node = out_node.add(name, "");
this->Serialize(in_data, child_node);
}
template <typename T>
void Deserialize(const Node& in_node, const string_t& name, T& out_data)
{
auto& child_node = in_node.get_child(name);
this->Deserialize(child_node, out_data);
}
Serialize()では、指定された名前(メンバ変数名など)で子ノード(chile_node)を追加し、子ノードに対してSerialize()を呼び出し入力データをノードに追加します。
Deserialize()では、指定された名前(メンバ変数名など)で子ノード(chile_node)を取得し、子ノードを指定してDeserialize()を呼び出して、データを復元します。
ツリーノード ⇔ アーカイブ変換
ツリーノードとアーカイブを相互変換する関数を用意しています。
この時に使用するboostライブラリの関数を使い分けることで、XML形式とJSON形式を切り替えることができます。
inline void WriteText(const Node& in_root_node, Archive& out_archive)
{
std::stringstream ss;
#if TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_XML
const int indent = 2;
auto setting = boost::property_tree::xml_parser::xml_writer_make_settings<string_t>(' ', indent, boost::property_tree::xml_parser::widen<string_t>("utf-8"));
boost::property_tree::write_xml(ss, in_root_node, setting);
#elif TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_JSON
boost::property_tree::write_json(ss, in_root_node);
#else
#error Invalid Serialize Mode TEXT_SERIALIZE_MODE
#endif
string_t in_str = ss.str();
size_t text_len = in_str.length();
size_t buffer_size = text_len + 1;
out_archive.Reserve(buffer_size);
size_t offset = 0;
out_archive.Write(in_str, offset);
}
inline void ReadText(const Archive& in_archive, Node& out_root_node)
{
std::stringstream ss;
ss << in_archive.GetDataPtr();
#if TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_XML
boost::property_tree::read_xml(ss, out_root_node);
#elif TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_JSON
boost::property_tree::read_json(ss, out_root_node);
#else
#error Invalid Serialize Mode TEXT_SERIALIZE_MODE
#endif
}
WriteText()では、シリアライズしたツリーノードをテキストに変換し、アーカイブにセットします。
boost::property_tree::write_xml()またはboost::property_tree::write_json()を呼び出すことでツリーノードからテキスト(XML/JSON)に変換し、文字列ストリームに追加することができます。
XMLの場合には、boost::property_tree::xml_parser::xml_writer_make_settings<string_t>()で、XMLに変換する際の設定(インデントなど)を変更できます。
その後、文字列ストリームに追加したテキストデータを文字列に変換し、文字列長 + 終端にnull文字('\0')の長さ分アーカイブのメモリを確保し、文字列データをバイト配列としてアーカイブにコピーしています。
ReadText()では、アーカイブをテキストに変換し、ツリーノードに変換します。
アーカイブのバッファデータのポインタをそのまま文字列ストリームに追加することでテキストデータを取り出すことができます。
その後、boost::property_tree::read_xml()またはboost::property_tree::read_json()を呼び出すことでテキストデータ(XML/JSON)をパースし、ツリーノードに変換することができます。
ソース全体は以下の通りです。
#pragma once
#include "CommonTypes.h"
#include "TypeTraits.h"
#include "DataTypeTraits.h"
#include "Archive.h"
#include "FloatIntConverter.h"
#include "AppException.h"
#include "StringFormat.h"
#include <sstream>
#include <iomanip>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/json_parser.hpp>
#define TEXT_SERIALIZE_MODE_XML (0)
#define TEXT_SERIALIZE_MODE_JSON (1)
#define TEXT_SERIALIZE_MODE (TEXT_SERIALIZE_MODE_JSON)
namespace cpp_lib
{
namespace serialization
{
class TextSerialization
{
public:
using Node = boost::property_tree::ptree;
public:
static TextSerialization& GetInstance()
{
static TextSerialization instance;
return instance;
}
private:
TextSerialization() = default;
~TextSerialization() = default;
public:
TextSerialization(const TextSerialization&) = delete;
TextSerialization(TextSerialization&&) = delete;
TextSerialization& operator=(const TextSerialization&) = delete;
TextSerialization& operator=(TextSerialization&&) = delete;
public:
template <typename T>
void Serialize(const T& in_data, const string_t& name, serialization::Archive& out_archive)
{
Node root_node{};
this->Serialize(in_data, name, root_node);
this->WriteText(root_node, out_archive);
}
template <typename T>
void Deserialize(const serialization::Archive& in_archive, const string_t& name, T& out_data)
{
Node root_node{};
this->ReadText(in_archive, root_node);
this->Deserialize(root_node, name, out_data);
}
private:
template<typename T, cpp_lib::type_traits::concept_t<std::is_integral<T>::value> = nullptr>
void Serialize(const T& in_data, Node& out_node)
{
out_node.put_value(in_data);
}
template<typename T, cpp_lib::type_traits::concept_t<std::is_integral<T>::value> = nullptr>
void Deserialize(const Node& in_node, T& out_data)
{
if (auto value = in_node.get_value_optional<T>())
{
out_data = value.get();
}
else
{
THROW_APP_EXCEPTION("Value is nothing");
}
}
template<typename T, cpp_lib::type_traits::concept_t<std::is_floating_point<T>::value> = nullptr>
void Serialize(const T& in_data, Node& out_node)
{
string_t in_str{};
this->ToHexString(in_data, in_str);
out_node.put_value(in_str);
}
template<typename T, cpp_lib::type_traits::concept_t<std::is_floating_point<T>::value> = nullptr>
void Deserialize(const Node& in_node, T& out_data)
{
string_t out_str{};
if (auto value = in_node.get_value_optional<string_t>())
{
out_str = value.get();
this->FromHexString(out_str, out_data);
}
else
{
THROW_APP_EXCEPTION("Value is nothing");
}
}
template<typename T, cpp_lib::type_traits::concept_t<std::is_enum<T>::value> = nullptr>
void Serialize(const T& in_data, Node& out_node)
{
this->Serialize(cpp_lib::type_traits::underlying_cast(in_data), out_node);
}
template<typename T, cpp_lib::type_traits::concept_t<std::is_enum<T>::value> = nullptr>
void Deserialize(const Node& in_node, T& out_data)
{
cpp_lib::type_traits::underlying_type_t<T> data{};
this->Deserialize(in_node, data);
out_data = static_cast<T>(data);
}
template <typename T, size_t N>
void Serialize(const std::array<T, N>& in_data, const string_t& name, Node& out_node)
{
auto& child_node = out_node.add(name, "");
for (const auto& item : in_data)
{
this->Serialize(item, "item", child_node);
}
}
template <typename T, size_t N>
void Deserialize(const Node& in_node, const string_t& name, std::array<T, N>& out_data)
{
auto& child_node = in_node.get_child(name);
size_t i = 0;
for (const auto& item_node_pair : child_node)
{
T item{};
this->Deserialize(item_node_pair.second, item);
out_data[i] = item;
i++;
}
}
template <typename T>
void Serialize(const std::vector<T>& in_data, const string_t& name, Node& out_node)
{
auto& child_node = out_node.add(name, "");
for (const auto& item : in_data)
{
this->Serialize(item, "item", child_node);
}
}
template <typename T>
void Deserialize(const Node& in_node, const string_t& name, std::vector<T>& out_data)
{
auto& child_node = in_node.get_child(name);
out_data.clear();
out_data.reserve(child_node.size());
for (const auto& item_node_pair : child_node)
{
T item{};
this->Deserialize(item_node_pair.second, item);
out_data.push_back(item);
}
}
template <typename T1, typename T2>
void Serialize(const std::pair<T1, T2>& in_data, Node& out_node)
{
this->Serialize(in_data.first, "first", out_node);
this->Serialize(in_data.second, "second", out_node);
}
template <typename T1, typename T2>
void Deserialize(const Node& in_node, std::pair<T1, T2>& out_data)
{
T1 first{};
this->Deserialize(in_node, "first", first);
T2 second{};
this->Deserialize(in_node, "second", second);
out_data = std::make_pair<T1, T2>(first, second);
}
template <typename TKey, typename TValue>
void Serialize(const std::map<TKey, TValue>& in_data, const string_t& name, Node& out_node)
{
auto& child_node = out_node.add(name, "");
for (const auto& item : in_data)
{
this->Serialize(item, "item", child_node);
}
}
template <typename TKey, typename TValue>
void Deserialize(const Node& in_node, const string_t& name, std::map<TKey, TValue>& out_data)
{
auto& child_node = in_node.get_child(name);
for (const auto& item_node_pair : child_node)
{
TKey key{};
this->Deserialize(item_node_pair.second, "first", key);
TValue value{};
this->Deserialize(item_node_pair.second, "second", value);
out_data.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(value));
}
}
template <typename TUPLE, size_t ...I>
void SerializeTupleImple(Node& out_node, TUPLE&& t, std::index_sequence<I...>)
{
using swallow = std::initializer_list<int>;
(void)swallow
{
(void(this->Serialize(std::get<1>(std::get<I>(t)), std::get<0>(std::get<I>(t)), out_node)), 0)...
};
}
template <typename TUPLE, size_t ...I>
void DeserializeTupleImple(const Node& in_node, TUPLE&& t, std::index_sequence<I...>)
{
using swallow = std::initializer_list<int>;
(void)swallow
{
(void(this->Deserialize(in_node, std::get<0>(std::get<I>(t)), std::get<1>(std::get<I>(t)))), 0)...
};
}
template <typename TUPLE>
void SerializeTuple(Node& out_node, TUPLE&& t)
{
this->SerializeTupleImple(out_node, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
}
template <typename TUPLE>
void DeserializeTuple(const Node& in_node, TUPLE&& t)
{
this->DeserializeTupleImple(in_node, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
}
template<class T>
void Serialize(const T& in_data, Node& out_node, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
auto tuple = cpp_lib::type_traits::DataTypeTraits<T>::GetNamedMembersAsTuple(in_data);
this->SerializeTuple(out_node, tuple);
}
template<class T>
void Deserialize(const Node& in_node, T& out_data, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
auto tuple = cpp_lib::type_traits::DataTypeTraits<T>::GetNamedMembersAsTuple(out_data);
this->DeserializeTuple(in_node, tuple);
}
template <typename T>
void Serialize(const T& in_data, const string_t& name, Node& out_node)
{
auto& child_node = out_node.add(name, "");
this->Serialize(in_data, child_node);
}
template <typename T>
void Deserialize(const Node& in_node, const string_t& name, T& out_data)
{
auto& child_node = in_node.get_child(name);
this->Deserialize(child_node, out_data);
}
private:
inline void Serialize(const string_t& in_data, Node& out_node)
{
out_node.put_value(in_data);
}
inline void Deserialize(const Node& in_node, string_t& out_data)
{
if (auto value = in_node.get_value_optional<string_t>())
{
out_data = value.get();
}
else
{
THROW_APP_EXCEPTION("Value is nothing");
}
}
inline void WriteText(const Node& in_root_node, serialization::Archive& out_archive)
{
std::stringstream ss;
#if TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_XML
const int indent = 2;
auto setting = boost::property_tree::xml_parser::xml_writer_make_settings<string_t>(' ', indent, boost::property_tree::xml_parser::widen<string_t>("utf-8"));
boost::property_tree::write_xml(ss, in_root_node, setting);
#elif TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_JSON
boost::property_tree::write_json(ss, in_root_node);
#else
#error Invalid Serialize Mode TEXT_SERIALIZE_MODE
#endif
string_t in_str = ss.str();
size_t text_len = in_str.length();
size_t buffer_size = text_len + 1;
out_archive.Reserve(buffer_size);
size_t offset = 0;
out_archive.Write(in_str, offset);
}
inline void ReadText(const serialization::Archive& in_archive, Node& out_root_node)
{
std::stringstream ss;
ss << in_archive.GetDataPtr();
#if TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_XML
boost::property_tree::read_xml(ss, out_root_node);
#elif TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_JSON
boost::property_tree::read_json(ss, out_root_node);
#else
#error Invalid Serialize Mode TEXT_SERIALIZE_MODE
#endif
}
inline void ToHexString(const float32_t& in_data, string_t& out_data)
{
std::stringstream ss;
cpp_lib::binary::FloatIntConverter<float32_t, uint32_t> converter;
uint32_t int_data = converter.ConvertToInt(in_data);
ss << std::showbase;
ss << std::setw(4);
ss << std::hex << int_data;
out_data = ss.str();
}
inline void ToHexString(const float64_t& in_data, string_t& out_data)
{
std::stringstream ss;
cpp_lib::binary::FloatIntConverter<float64_t, uint64_t> converter;
uint64_t int_data = converter.ConvertToInt(in_data);
ss << std::showbase;
ss << std::setw(8);
ss << std::hex << int_data;
out_data = ss.str();
}
inline void FromHexString(const string_t& in_data, float32_t& out_data)
{
uint32_t int_data = std::stoul(in_data, nullptr, 16);
cpp_lib::binary::FloatIntConverter<float32_t, uint32_t> converter;
out_data = converter.ConvertToFloat(int_data);
}
inline void FromHexString(const string_t& in_data, float64_t& out_data)
{
uint64_t int_data = std::stoull(in_data, nullptr, 16);
cpp_lib::binary::FloatIntConverter<float64_t, uint64_t> converter;
out_data = converter.ConvertToFloat(int_data);
}
};
}
}
あとがき
今回はテキスト形式でのシリアライズ処理を実装しました。
次回は、任意のデータ型で実際にシリアライズを行う方法について説明します。