Last active
January 30, 2020 19:46
-
-
Save meitinger/fe83b21ae265eda2060f60a38f21a956 to your computer and use it in GitHub Desktop.
Serialization Framework for RapidJSON
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (C) 2020 Manuel Meitinger | |
// | |
// Licensed under the MIT License (the "License"); you may not use this file except | |
// in compliance with the License. You may obtain a copy of the License at | |
// | |
// http://opensource.org/licenses/MIT | |
// | |
// Unless required by applicable law or agreed to in writing, software distributed | |
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR | |
// CONDITIONS OF ANY KIND, either express or implied. See the License for the | |
// specific language governing permissions and limitations under the License. | |
#ifndef RAPIDJSON_PERSIST_H_ | |
#define RAPIDJSON_PERSIST_H_ | |
#include "document.h" | |
#include <algorithm> | |
#include <array> | |
#include <bitset> | |
#include <complex> | |
#include <deque> | |
#include <functional> | |
#include <limits> | |
#include <list> | |
#include <map> | |
#include <memory> | |
#include <optional> | |
#include <set> | |
#include <string> | |
#include <string_view> | |
#include <tuple> | |
#include <type_traits> | |
#include <typeinfo> | |
#include <unordered_map> | |
#include <unordered_set> | |
#include <vector> | |
RAPIDJSON_NAMESPACE_BEGIN | |
namespace persist { | |
/////////////////////////////////////////////////////////////////////////////// | |
// unicode | |
#ifndef RAPIDJSON_PERSIST_UNICODE | |
#if defined(UNICODE) || defined(_UNICODE) | |
#define RAPIDJSON_PERSIST_UNICODE 1 | |
#else | |
#define RAPIDJSON_PERSIST_UNICODE 0 | |
#endif | |
#endif | |
/////////////////////////////////////////////////////////////////////////////// | |
// TypedPointers, NamedObjects | |
using type_info_ref = std::reference_wrapper<const std::type_info>; | |
struct type_info_ref_hash { | |
std::size_t operator()(type_info_ref code) const noexcept { return code.get().hash_code(); } | |
}; | |
struct type_info_ref_equal_to { | |
bool operator()(type_info_ref lhs, type_info_ref rhs) const noexcept { return lhs.get() == rhs.get(); } | |
}; | |
using TypedPointers = std::unordered_map<type_info_ref, void *, type_info_ref_hash, type_info_ref_equal_to>; | |
template <typename Encoding> | |
using NamedObjects = std::unordered_map<std::basic_string<typename Encoding::Ch>, TypedPointers>; | |
/////////////////////////////////////////////////////////////////////////////// | |
// macros, primary serializer | |
#define RAPIDJSON_PERSIST_TEMPLATE_INTERNAL typename Encoding, typename HeapAllocator, typename StackAllocator | |
#define RAPIDJSON_PERSIST_TEMPLATE template <RAPIDJSON_PERSIST_TEMPLATE_INTERNAL> | |
#define RAPIDJSON_PERSIST_DOCUMENT GenericDocument<Encoding, HeapAllocator, StackAllocator> | |
#define RAPIDJSON_PERSIST_VALUE GenericValue<Encoding, HeapAllocator> | |
#define RAPIDJSON_PERSIST_SERIALIZE_INTERNAL(name, ...) \ | |
RAPIDJSON_PERSIST_VALUE name(const __VA_ARGS__ &value, RAPIDJSON_PERSIST_DOCUMENT &doc) | |
#define RAPIDJSON_PERSIST_DESERIALIZE_INTERNAL(name, ...) \ | |
bool name(const RAPIDJSON_PERSIST_VALUE &json, __VA_ARGS__ &value, const RAPIDJSON_PERSIST_DOCUMENT &doc, \ | |
NamedObjects<Encoding> &objects) | |
#define RAPIDJSON_PERSIST_SERIALIZE(...) \ | |
RAPIDJSON_PERSIST_TEMPLATE static RAPIDJSON_PERSIST_SERIALIZE_INTERNAL(serialize, __VA_ARGS__) | |
#define RAPIDJSON_PERSIST_DESERIALIZE(...) \ | |
RAPIDJSON_PERSIST_TEMPLATE static RAPIDJSON_PERSIST_DESERIALIZE_INTERNAL(deserialize, __VA_ARGS__) | |
template <typename T, typename Enable = void> | |
struct serializer { | |
RAPIDJSON_PERSIST_SERIALIZE(T) { return value.serialize(doc); } | |
RAPIDJSON_PERSIST_DESERIALIZE(T) { return value.deserialize(json, doc, objects); } | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// formatters (char*, std::*string, std::bitset) | |
template <typename T, typename Enable = void> | |
struct formatter; | |
template <typename Char> | |
struct formatter<Char, std::enable_if_t<std::is_same_v<Char, char> || std::is_same_v<Char, wchar_t>>> { | |
template <typename Encoding> | |
static std::enable_if_t<std::is_same_v<typename Encoding::Ch, Char>, std::basic_string<Char>> | |
format(const Char &value) | |
{ | |
return std::basic_string<Char>(&value, 1); | |
} | |
template <typename Encoding> | |
static std::enable_if_t<std::is_same_v<typename Encoding::Ch, Char>, Char> | |
parse(const std::basic_string<typename Encoding::Ch> &str) | |
{ | |
if (str.length() != 1) { | |
throw std::logic_error("single character expected"); | |
} | |
return str[0]; | |
} | |
}; | |
template <typename Char, typename Traits, typename Allocator> | |
struct formatter<std::basic_string<Char, Traits, Allocator>> { | |
template <typename Encoding> | |
static std::enable_if_t<std::is_same_v<typename Encoding::Ch, Char>, std::basic_string<Char>> | |
format(const std::basic_string<Char, Traits, Allocator> &value) | |
{ | |
return std::basic_string<Char>(value.data(), value.length()); | |
} | |
template <typename Encoding> | |
static std::enable_if_t<std::is_same_v<typename Encoding::Ch, Char>, std::basic_string<Char, Traits, Allocator>> | |
parse(const std::basic_string<Char> &str) | |
{ | |
return std::basic_string<Char, Traits, Allocator>(str.data(), str.length()); | |
} | |
}; | |
template <std::size_t N> | |
struct formatter<std::bitset<N>> { | |
template <typename Encoding> | |
static std::basic_string<typename Encoding::Ch> format(const std::bitset<N> &value) | |
{ | |
using Char = typename Encoding::Ch; | |
return value.template to_string<Char, std::char_traits<Char>, std::allocator<Char>>(); | |
} | |
template <typename Encoding> | |
static std::bitset<N> parse(const std::basic_string<typename Encoding::Ch> &str) | |
{ | |
return std::bitset<N>(str); | |
} | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// literals | |
template <typename Char> | |
struct literals; | |
#define RAPIDJSON_PERIST_LITERALS(type, p) \ | |
template <> \ | |
struct literals<type> { \ | |
static constexpr const auto ref = std::basic_string_view<type>(p##"ref:"); \ | |
static constexpr const auto main = std::basic_string_view<type>(p##"main"); \ | |
static constexpr const auto key = std::basic_string_view<type>(p##"key"); \ | |
static constexpr const auto value = std::basic_string_view<type>(p##"value"); \ | |
static constexpr const auto real = std::basic_string_view<type>(p##"r"); \ | |
static constexpr const auto imag = std::basic_string_view<type>(p##"i"); \ | |
static constexpr const auto sep = std::basic_string_view<type>(p##" | "); \ | |
static constexpr const auto sep_char = p##'|'; \ | |
} | |
RAPIDJSON_PERIST_LITERALS(char, ); | |
RAPIDJSON_PERIST_LITERALS(wchar_t, L); | |
#undef RAPIDJSON_PERIST_LITERALS | |
/////////////////////////////////////////////////////////////////////////////// | |
// fundamental types | |
#define RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE(type, conv_type, name) \ | |
template <> \ | |
struct serializer<type> { \ | |
RAPIDJSON_PERSIST_SERIALIZE(type) { return RAPIDJSON_PERSIST_VALUE(static_cast<conv_type>(value)); } \ | |
RAPIDJSON_PERSIST_DESERIALIZE(type) \ | |
{ \ | |
if (!json.Is##name()) { \ | |
return false; \ | |
} \ | |
const auto raw_value = json.Get##name(); \ | |
if constexpr (!std::is_same_v<decltype(raw_value), type>) { \ | |
if (raw_value < std::numeric_limits<type>::min() || raw_value > std::numeric_limits<type>::max()) { \ | |
return false; \ | |
} \ | |
} \ | |
value = static_cast<type>(raw_value); \ | |
return true; \ | |
} \ | |
} | |
#define RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(type, conv_type, name, parser) \ | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE(type, conv_type, name); \ | |
template <> \ | |
struct formatter<type> { \ | |
template <typename Encoding> \ | |
static std::enable_if_t<std::is_same_v<typename Encoding::Ch, char>, std::string> format(const type &value) \ | |
{ \ | |
return std::to_string(static_cast<conv_type>(value)); \ | |
} \ | |
template <typename Encoding> \ | |
static std::enable_if_t<std::is_same_v<typename Encoding::Ch, wchar_t>, std::wstring> \ | |
format(const type &value) \ | |
{ \ | |
return std::to_wstring(static_cast<conv_type>(value)); \ | |
} \ | |
template <typename Encoding> \ | |
static type parse(const std::basic_string<typename Encoding::Ch> &str) \ | |
{ \ | |
const auto value = parser(str); \ | |
if (value < std::numeric_limits<type>::min() || value > std::numeric_limits<type>::max()) { \ | |
throw std::out_of_range(#type " value is out of bounds"); \ | |
} \ | |
return static_cast<type>(value); \ | |
} \ | |
} | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE(bool, bool, Bool); | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(signed char, int, Int, std::stoi); | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(unsigned char, unsigned int, Uint, std::stoul); | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(short int, int, Int, std::stoi); | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(unsigned short int, unsigned int, Uint, std::stoul); | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(int, int, Int, std::stoi); | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(unsigned int, unsigned int, Uint, std::stoul); | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(long int, int64_t, Int64, std::stol); | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(unsigned long int, uint64_t, Uint64, std::stoul); | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(long long int, int64_t, Int64, std::stoll); | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(unsigned long long int, uint64_t, Uint64, std::stoull); | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(float, float, Float, std::stof); | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(double, double, Double, std::stod); | |
RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE(long double, double, Double, std::stold); | |
#undef RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE | |
#undef RAPIDJSON_PERSIST_FUNDAMENTAL_TYPE_FORMATTABLE | |
/////////////////////////////////////////////////////////////////////////////// | |
// formattable | |
template <typename T> | |
struct serializer<T, std::enable_if_t<std::is_constructible_v<formatter<T>>>> { | |
RAPIDJSON_PERSIST_SERIALIZE(T) | |
{ | |
const auto str = formatter<T>::template format<Encoding>(value); | |
return RAPIDJSON_PERSIST_VALUE(str.data(), str.length(), doc.GetAllocator()); | |
} | |
RAPIDJSON_PERSIST_DESERIALIZE(T) | |
{ | |
if (!json.IsString()) { | |
return false; | |
} | |
try { | |
value = formatter<T>::template parse<Encoding>( | |
std::basic_string<typename Encoding::Ch>(json.GetString(), json.GetStringLength())); | |
} catch (const std::logic_error &) { | |
return false; | |
} | |
return true; | |
} | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// std::shared_ptr | |
template <typename T> | |
struct serializer<std::shared_ptr<T>> { | |
RAPIDJSON_PERSIST_SERIALIZE(std::shared_ptr<T>) | |
{ | |
using Char = typename Encoding::Ch; | |
if (!value) { | |
return RAPIDJSON_PERSIST_VALUE(); | |
} | |
const auto name = formatter<uintptr_t>::template format<Encoding>(reinterpret_cast<uintptr_t>(value.get())); | |
if (doc.FindMember(StringRef(name.data(), name.size())) == doc.MemberEnd()) { | |
doc.AddMember(RAPIDJSON_PERSIST_VALUE(name.data(), name.size(), doc.GetAllocator()), | |
serializer<T>::serialize(*value, doc), doc.GetAllocator()); | |
} | |
const auto ref_name = | |
std::basic_string<Char>(literals<Char>::ref).append(std::basic_string_view(name.data(), name.size())); | |
return RAPIDJSON_PERSIST_VALUE(ref_name.data(), ref_name.length(), doc.GetAllocator()); | |
} | |
RAPIDJSON_PERSIST_DESERIALIZE(std::shared_ptr<T>) | |
{ | |
using Char = typename Encoding::Ch; | |
// reset on null value | |
if (json.IsNull()) { | |
value.reset(); | |
return true; | |
} | |
// get the name part from the "ref:<name>" value | |
if (!json.IsString()) { | |
return false; | |
} | |
const auto ref_name = std::basic_string_view(json.GetString(), json.GetStringLength()); | |
if (ref_name.compare(0, literals<Char>::ref.length(), literals<Char>::ref) != 0) { | |
return false; | |
} | |
const auto name = std::basic_string<Char>(ref_name.substr(literals<Char>::ref.length())); | |
// return a deserialized shared_ptr, if any | |
const auto &type_id = typeid(std::shared_ptr<T>); | |
const auto type_ptrs_entry = objects.find(name); | |
if (type_ptrs_entry != objects.end()) { | |
const auto ptr_entry = type_ptrs_entry->second.find(type_id); | |
if (ptr_entry != type_ptrs_entry->second.end()) { | |
value = *reinterpret_cast<std::shared_ptr<T> *>(ptr_entry->second); | |
return true; | |
} | |
} | |
// find the actual value and deserialize it, storing the shared_ptr | |
const auto entry = doc.FindMember(StringRef(name.data(), name.length())); | |
if (entry == doc.MemberEnd()) { | |
return false; | |
} | |
if (type_ptrs_entry == objects.end()) { | |
objects[name] = TypedPointers(); | |
} | |
auto &ptrs = objects[name]; | |
value = std::make_shared<T>(); | |
ptrs[type_id] = &value; // add to map *before* deserializing | |
if (!serializer<T>::deserialize(entry->value, *value, doc, objects)) { | |
ptrs.erase(type_id); // remove on failure | |
return false; | |
} | |
return true; | |
} | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// std::complex | |
template <typename T> | |
struct serializer<std::complex<T>> { | |
RAPIDJSON_PERSIST_SERIALIZE(std::complex<T>) | |
{ | |
using Char = typename Encoding::Ch; | |
auto json = RAPIDJSON_PERSIST_VALUE(Type::kObjectType); | |
json.AddMember(StringRef(literals<Char>::real.data(), literals<Char>::real.length()), | |
serializer<T>::serialize(value.real(), doc), doc.GetAllocator()); | |
json.AddMember(StringRef(literals<Char>::imag.data(), literals<Char>::imag.length()), | |
serializer<T>::serialize(value.imag(), doc), doc.GetAllocator()); | |
return json; | |
} | |
RAPIDJSON_PERSIST_DESERIALIZE(std::complex<T>) | |
{ | |
using Char = typename Encoding::Ch; | |
if (!json.IsObject()) { | |
return false; | |
} | |
auto success = true; | |
auto real = T(); | |
auto real_member = json.FindMember(StringRef(literals<Char>::real.data(), literals<Char>::real.length())); | |
if (real_member != json.MemberEnd() && serializer<T>::deserialize(real_member->value, real, doc, objects)) { | |
value.real(real); | |
} else { | |
success = false; | |
} | |
auto imag = T(); | |
auto imag_member = json.FindMember(StringRef(literals<Char>::imag.data(), literals<Char>::imag.length())); | |
if (imag_member != json.MemberEnd() && serializer<T>::deserialize(imag_member->value, imag, doc, objects)) { | |
value.imag(imag); | |
} else { | |
success = false; | |
} | |
return success; | |
} | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// std::unique_ptr (no custom deleters), std::optional | |
template <template <typename> typename Holder, typename T> | |
struct serializer<Holder<T>, std::enable_if_t<std::is_same_v<std::unique_ptr<T>, Holder<T>> || | |
std::is_same_v<std::optional<T>, Holder<T>>>> { | |
RAPIDJSON_PERSIST_SERIALIZE(Holder<T>) | |
{ | |
return value ? serializer<T>::serialize(*value, doc) : RAPIDJSON_PERSIST_VALUE(); | |
} | |
RAPIDJSON_PERSIST_DESERIALIZE(Holder<T>) | |
{ | |
if (json.IsNull()) { | |
value.reset(); | |
return true; | |
} | |
if constexpr (std::is_same_v<std::unique_ptr<T>, Holder<T>>) { | |
value = std::make_unique<T>(); | |
} else { | |
value = std::make_optional<T>(); | |
} | |
return serializer<T>::deserialize(json, *value, doc, objects); | |
} | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// enums | |
template <typename Enum, typename Char> | |
struct enum_format; | |
template <typename Char> | |
static std::enable_if_t<std::is_same_v<Char, char>, bool> is_space(Char ch) | |
{ | |
return std::isspace(ch) != 0; | |
} | |
template <typename Char> | |
static std::enable_if_t<std::is_same_v<Char, wchar_t>, bool> is_space(Char ch) | |
{ | |
return std::iswspace(ch) != 0; | |
} | |
template <typename Enum> | |
struct formatter<Enum, std::enable_if_t<std::is_enum_v<Enum>>> { | |
using underlying_type = std::underlying_type_t<Enum>; | |
template <typename Encoding> | |
static std::basic_string<typename Encoding::Ch> format(const Enum &value) | |
{ | |
using Char = typename Encoding::Ch; | |
using T = enum_format<Enum, Char>; | |
auto str = std::basic_string<Char>(); | |
auto first = true; | |
auto raw_value = static_cast<underlying_type>(value); | |
if constexpr (std::is_constructible_v<T>) { | |
for (const auto &mask : T::masks) { | |
const auto &mask_raw = mask.second; | |
for (const auto &[value_name, value_raw] : T::values) { | |
if ((value_raw & mask_raw) && !(value_raw & ~mask_raw) && ((raw_value & mask_raw) == value_raw)) { | |
if (first) { | |
first = false; | |
} else { | |
str.append(literals<Char>::sep); | |
} | |
str.append(value_name); | |
raw_value &= ~mask_raw; | |
} | |
} | |
} | |
for (const auto &[flag_name, flag_raw] : T::flags) { | |
if ((flag_raw & raw_value) == flag_raw) { | |
if (first) { | |
first = false; | |
} else { | |
str.append(literals<Char>::sep); | |
} | |
str.append(flag_name); | |
raw_value &= ~flag_raw; | |
} | |
} | |
for (const auto &[value_name, value_raw] : T::values) { | |
if (value_raw == raw_value) { | |
if (first) { | |
first = false; | |
} else { | |
str.append(literals<Char>::sep); | |
} | |
str.append(value_name); | |
return str; | |
} | |
} | |
} | |
if (!first) { | |
if (!raw_value) { | |
return str; | |
} | |
str.append(literals<Char>::sep); | |
} | |
str.append(formatter<underlying_type>::template format<Encoding>(raw_value)); | |
return str; | |
} | |
template <typename Encoding> | |
static Enum parse(const std::basic_string<typename Encoding::Ch> &str) | |
{ | |
using Char = typename Encoding::Ch; | |
using T = enum_format<Enum, Char>; | |
constexpr const auto npos = std::basic_string<Char>::npos; | |
auto raw_value = underlying_type(); | |
auto is_empty = true; | |
for (auto offset = std::size_t(0), sep_pos = std::size_t(0); sep_pos != npos; offset = sep_pos + 1) { | |
while (offset < str.length() && is_space(str[offset])) { | |
offset++; | |
} | |
sep_pos = str.find(literals<Char>::sep_char, offset); | |
auto offset_end = sep_pos == npos ? str.length() : sep_pos; | |
while (offset_end > offset && is_space(str[offset_end - 1])) { | |
offset_end--; | |
} | |
if (offset_end <= offset) { | |
continue; | |
} | |
is_empty = false; | |
const auto part = std::basic_string<Char>(str, offset, offset_end - offset); | |
if constexpr (std::is_constructible_v<T>) { | |
const auto flag = T::flags.find(part); | |
if (flag != T::flags.end()) { | |
raw_value |= flag->second; | |
continue; | |
} | |
const auto enum_value = T::values.find(part); | |
if (enum_value != T::values.end()) { | |
raw_value |= enum_value->second; | |
continue; | |
} | |
} | |
raw_value |= formatter<underlying_type>::template parse<Encoding>(part); | |
} | |
if (is_empty) { | |
throw std::logic_error("empty enum value"); | |
}; | |
return static_cast<Enum>(raw_value); | |
} | |
}; | |
// clang-format off | |
#define RAPIDJSON_PERSIST_ENUM_BEGIN_(char_type, enum_type) \ | |
namespace RAPIDJSON_NAMESPACE::persist { \ | |
template <> \ | |
struct enum_format<enum_type, char_type> { \ | |
using type = enum_type; \ | |
using underlying_type = std::underlying_type_t<enum_type>; \ | |
private: \ | |
static inline const auto _maps = ([](){ \ | |
std::unordered_map<std::basic_string<char_type>, underlying_type> _flags, _masks, _values; | |
#define RAPIDJSON_PERSIST_ENUM_VALUE_(prefix, char_type, set, name) \ | |
set[std::basic_string<char_type>(prefix## #name)] = static_cast<underlying_type>(type::name) | |
#define RAPIDJSON_PERSIST_ENUM_END() \ | |
return std::make_tuple(_flags, _masks, _values); \ | |
})(); \ | |
public: \ | |
static inline const auto &flags = std::get<0>(_maps); \ | |
static inline const auto &masks = std::get<1>(_maps); \ | |
static inline const auto &values = std::get<2>(_maps); \ | |
}; \ | |
} | |
// clang-format on | |
#define RAPIDJSON_PERSIST_ENUM_BEGIN_A(type) RAPIDJSON_PERSIST_ENUM_BEGIN_(char, type) | |
#define RAPIDJSON_PERSIST_ENUM_BEGIN_W(type) RAPIDJSON_PERSIST_ENUM_BEGIN_(wchar_t, type) | |
#define RAPIDJSON_PERSIST_ENUM_FLAG_A(name) RAPIDJSON_PERSIST_ENUM_VALUE_(, char, _flags, name) | |
#define RAPIDJSON_PERSIST_ENUM_FLAG_W(name) RAPIDJSON_PERSIST_ENUM_VALUE_(L, wchar_t, _flags, name) | |
#define RAPIDJSON_PERSIST_ENUM_MASK_A(name) RAPIDJSON_PERSIST_ENUM_VALUE_(, char, _masks, name) | |
#define RAPIDJSON_PERSIST_ENUM_MASK_W(name) RAPIDJSON_PERSIST_ENUM_VALUE_(L, wchar_t, _masks, name) | |
#define RAPIDJSON_PERSIST_ENUM_VALUE_A(name) RAPIDJSON_PERSIST_ENUM_VALUE_(, char, _values, name) | |
#define RAPIDJSON_PERSIST_ENUM_VALUE_W(name) RAPIDJSON_PERSIST_ENUM_VALUE_(L, wchar_t, _values, name) | |
#if RAPIDJSON_PERSIST_UNICODE | |
#define RAPIDJSON_PERSIST_ENUM_BEGIN RAPIDJSON_PERSIST_ENUM_BEGIN_W | |
#define RAPIDJSON_PERSIST_ENUM_FLAG RAPIDJSON_PERSIST_ENUM_FLAG_W | |
#define RAPIDJSON_PERSIST_ENUM_MASK RAPIDJSON_PERSIST_ENUM_MASK_W | |
#define RAPIDJSON_PERSIST_ENUM_VALUE RAPIDJSON_PERSIST_ENUM_VALUE_W | |
#else | |
#define RAPIDJSON_PERSIST_ENUM_BEGIN RAPIDJSON_PERSIST_ENUM_BEGIN_A | |
#define RAPIDJSON_PERSIST_ENUM_FLAG RAPIDJSON_PERSIST_ENUM_FLAG_A | |
#define RAPIDJSON_PERSIST_ENUM_MASK RAPIDJSON_PERSIST_ENUM_MASK_A | |
#define RAPIDJSON_PERSIST_ENUM_VALUE RAPIDJSON_PERSIST_ENUM_VALUE_A | |
#endif | |
/////////////////////////////////////////////////////////////////////////////// | |
// std::unordered_map, std::map | |
template <typename Key, typename T, typename Map, RAPIDJSON_PERSIST_TEMPLATE_INTERNAL> | |
static RAPIDJSON_PERSIST_SERIALIZE_INTERNAL(serialize_map, Map) | |
{ | |
using Char = typename Encoding::Ch; | |
if constexpr (std::is_constructible_v<formatter<Key>>) { | |
auto json = RAPIDJSON_PERSIST_VALUE(Type::kObjectType); | |
for (const auto &[key, sub_value] : value) { | |
const auto name = formatter<Key>::template format<Encoding>(key); | |
json.AddMember(RAPIDJSON_PERSIST_VALUE(name.data(), name.length(), doc.GetAllocator()), | |
serializer<T>::serialize(sub_value, doc), doc.GetAllocator()); | |
} | |
return json; | |
} else { | |
auto json = RAPIDJSON_PERSIST_VALUE(Type::kArrayType); | |
for (const auto &[key, sub_value] : value) { | |
auto entry = RAPIDJSON_PERSIST_VALUE(Type::kObjectType); | |
entry.AddMember(StringRef(literals<Char>::key.data(), literals<Char>::key.length()), | |
serializer<Key>::serialize(key, doc), doc.GetAllocator()); | |
entry.AddMember(StringRef(literals<Char>::value.data(), literals<Char>::value.length()), | |
serializer<T>::serialize(sub_value, doc), doc.GetAllocator()); | |
json.PushBack(entry, doc.GetAllocator()); | |
} | |
return json; | |
} | |
} | |
template <typename Key, typename T, typename Map, RAPIDJSON_PERSIST_TEMPLATE_INTERNAL> | |
static RAPIDJSON_PERSIST_DESERIALIZE_INTERNAL(deserialize_map, Map) | |
{ | |
using Char = typename Encoding::Ch; | |
if constexpr (std::is_constructible_v<formatter<Key>>) { | |
if (!json.IsObject()) { | |
return false; | |
} | |
auto success = true; | |
value.clear(); | |
for (const auto &entry : json.GetObject()) { | |
const auto name = std::basic_string<Char>(entry.name.GetString(), entry.name.GetStringLength()); | |
auto index = Key(); | |
try { | |
index = formatter<Key>::template parse<Encoding>(name); | |
} catch (const std::logic_error &) { | |
success = false; | |
continue; | |
} | |
const auto inserted = value.emplace(std::piecewise_construct, std::make_tuple(index), std::make_tuple()); | |
success &= serializer<T>::deserialize(entry.value, inserted.first->second, doc, objects); | |
} | |
return success; | |
} else { | |
if (!json.IsArray()) { | |
return false; | |
} | |
auto success = true; | |
value.clear(); | |
for (const auto &element : json.GetArray()) { | |
if (!element.IsObject()) { | |
success = false; | |
continue; | |
} | |
const auto key_entry = | |
element.FindMember(StringRef(literals<Char>::key.data(), literals<Char>::key.length())); | |
if (key_entry == element.MemberEnd()) { | |
success = false; | |
continue; | |
} | |
auto index = Key(); | |
if (!serializer<Key>::deserialize(key_entry->value, index, doc, objects)) { | |
success = false; | |
continue; | |
} | |
const auto inserted = value.emplace(std::piecewise_construct, std::make_tuple(index), std::make_tuple()); | |
const auto value_entry = | |
element.FindMember(StringRef(literals<Char>::value.data(), literals<Char>::value.length())); | |
success &= value_entry != element.MemberEnd() && | |
serializer<T>::deserialize(value_entry->value, inserted.first->second, doc, objects); | |
} | |
return success; | |
} | |
} | |
template <typename Key, typename T, typename Hash, typename EqualTo, typename Allocator> | |
struct serializer<std::unordered_map<Key, T, Hash, EqualTo, Allocator>> { | |
RAPIDJSON_PERSIST_SERIALIZE(std::unordered_map<Key, T, Hash, EqualTo, Allocator>) | |
{ | |
return serialize_map<Key, T>(value, doc); | |
} | |
RAPIDJSON_PERSIST_DESERIALIZE(std::unordered_map<Key, T, Hash, EqualTo, Allocator>) | |
{ | |
return deserialize_map<Key, T>(json, value, doc, objects); | |
} | |
}; | |
template <typename Key, typename T, typename Less, typename Allocator> | |
struct serializer<std::map<Key, T, Less, Allocator>> { | |
RAPIDJSON_PERSIST_SERIALIZE(std::map<Key, T, Less, Allocator>) { return serialize_map<Key, T>(value, doc); } | |
RAPIDJSON_PERSIST_DESERIALIZE(std::map<Key, T, Less, Allocator>) | |
{ | |
return deserialize_map<Key, T>(json, value, doc, objects); | |
} | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// std::unordered_set, std::set | |
template <typename T, typename Set, RAPIDJSON_PERSIST_TEMPLATE_INTERNAL> | |
static RAPIDJSON_PERSIST_SERIALIZE_INTERNAL(serialize_set, Set) | |
{ | |
auto json = RAPIDJSON_PERSIST_VALUE(Type::kArrayType); | |
for (const auto &element : value) { | |
json.PushBack(serializer<T>::serialize(element, doc), doc.GetAllocator()); | |
} | |
return json; | |
} | |
template <typename T, typename Set, RAPIDJSON_PERSIST_TEMPLATE_INTERNAL> | |
static RAPIDJSON_PERSIST_DESERIALIZE_INTERNAL(deserialize_set, Set) | |
{ | |
if (!json.IsArray()) { | |
return false; | |
} | |
auto success = true; | |
value.clear(); | |
for (const auto &element : json.GetArray()) { | |
auto sub_value = T(); | |
if (serializer<T>::deserialize(element, sub_value, doc, objects)) { | |
value.insert(sub_value); | |
} else { | |
success = false; | |
} | |
} | |
return success; | |
} | |
template <typename T, typename Hash, typename EqualTo, typename Allocator> | |
struct serializer<std::unordered_set<T, Hash, EqualTo, Allocator>> { | |
RAPIDJSON_PERSIST_SERIALIZE(std::unordered_set<T, Hash, EqualTo, Allocator>) | |
{ | |
return serialize_set<T>(value, doc); | |
} | |
RAPIDJSON_PERSIST_DESERIALIZE(std::unordered_set<T, Hash, EqualTo, Allocator>) | |
{ | |
return deserialize_set<T>(json, value, doc, objects); | |
} | |
}; | |
template <typename T, typename Less, typename Allocator> | |
struct serializer<std::set<T, Less, Allocator>> { | |
RAPIDJSON_PERSIST_SERIALIZE(std::set<T, Less, Allocator>) { return serialize_set<T>(value, doc); } | |
RAPIDJSON_PERSIST_DESERIALIZE(std::set<T, Less, Allocator>) | |
{ | |
return deserialize_set<T>(json, value, doc, objects); | |
} | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// T[N], std::array | |
template <template <typename, std::size_t> typename Array, typename T, std::size_t N> | |
struct serializer< | |
Array<T, N>, std::enable_if_t<std::is_same_v<Array<T, N>, std::array<T, N>> || std::is_same_v<Array<T, N>, T[N]>>> { | |
RAPIDJSON_PERSIST_SERIALIZE(Array<T, N>) | |
{ | |
auto json = RAPIDJSON_PERSIST_VALUE(Type::kArrayType); | |
for (auto i = std::size_t(0); i < N; i++) { | |
json.PushBack(serializer<T>::serialize(value[i], doc), doc.GetAllocator()); | |
} | |
return json; | |
} | |
RAPIDJSON_PERSIST_DESERIALIZE(Array<T, N>) | |
{ | |
if (!json.IsArray()) { | |
return false; | |
} | |
auto success = true; | |
for (auto i = std::size_t(0), limit = std::min<std::size_t>(N, json.Size()); i < limit; i++) { | |
success &= serializer<T>::deserialize(json[i], value[i], doc, objects); | |
} | |
return success && json.Size() == N; | |
} | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// std::tuple | |
template <typename... Types> | |
struct serializer<std::tuple<Types...>> { | |
private: | |
template <std::size_t... Indices, typename Tuple, RAPIDJSON_PERSIST_TEMPLATE_INTERNAL> | |
static RAPIDJSON_PERSIST_DESERIALIZE_INTERNAL(deserialize_internal, std::index_sequence<Indices...>, Tuple) | |
{ | |
return (serializer<std::remove_reference_t<decltype(std::get<Indices>(value))>>::deserialize( | |
json[Indices], std::get<Indices>(value), doc, objects) & | |
...); | |
} | |
public: | |
RAPIDJSON_PERSIST_SERIALIZE(std::tuple<Types...>) | |
{ | |
auto json = RAPIDJSON_PERSIST_VALUE(Type::kArrayType); | |
std::apply( | |
[&json, &doc](const auto &... sub_value) { | |
(..., | |
json.PushBack(serializer<std::remove_const_t<std::remove_reference_t<decltype(sub_value)>>>::serialize( | |
sub_value, doc), | |
doc.GetAllocator())); | |
}, | |
value); | |
return json; | |
} | |
RAPIDJSON_PERSIST_DESERIALIZE(std::tuple<Types...>) | |
{ | |
if (!json.IsArray() || json.Size() != sizeof...(Types)) { | |
return false; | |
} | |
return deserialize_internal(json, std::index_sequence_for<Types...>{}, value, doc, objects); | |
} | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// std::vector, std::list, std::deque | |
template <typename T, template <typename, typename> typename U, typename Allocator> | |
struct serializer<U<T, Allocator>, std::enable_if_t<std::is_same_v<U<T, Allocator>, std::vector<T, Allocator>> || | |
std::is_same_v<U<T, Allocator>, std::list<T, Allocator>> || | |
std::is_same_v<U<T, Allocator>, std::deque<T, Allocator>>>> { | |
RAPIDJSON_PERSIST_SERIALIZE(U<T, Allocator>) | |
{ | |
auto json = RAPIDJSON_PERSIST_VALUE(Type::kArrayType); | |
for (const auto &element : value) { | |
json.PushBack(serializer<T>::serialize(element, doc), doc.GetAllocator()); | |
} | |
return json; | |
} | |
RAPIDJSON_PERSIST_DESERIALIZE(U<T, Allocator>) | |
{ | |
if (!json.IsArray()) { | |
return false; | |
} | |
auto success = true; | |
value.clear(); | |
for (const auto &element : json.GetArray()) { | |
auto &sub_value = value.emplace_back(); | |
success &= serializer<T>::deserialize(element, sub_value, doc, objects); | |
} | |
return success; | |
} | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// serializable | |
template <typename T, | |
typename Encoding = | |
#if RAPIDJSON_PERSIST_UNICODE | |
UTF16<> | |
#else | |
UTF8<> | |
#endif | |
, | |
typename HeapAllocator = MemoryPoolAllocator<>, typename StackAllocator = CrtAllocator> | |
class serializable { | |
private: | |
using Char = typename Encoding::Ch; | |
struct member_t { | |
private: | |
using serialize_t = std::function<RAPIDJSON_PERSIST_SERIALIZE_INTERNAL(, T)>; | |
using deserialize_t = std::function<RAPIDJSON_PERSIST_DESERIALIZE_INTERNAL(, T)>; | |
public: | |
member_t(const Char *name, bool checked, serialize_t serialize, deserialize_t deserialize) | |
: name(name), checked(checked), serialize(serialize), deserialize(deserialize) | |
{ | |
} | |
const Char *const name; | |
const bool checked; | |
const serialize_t serialize; | |
const deserialize_t deserialize; | |
}; | |
static inline auto members = std::list<member_t>(); | |
RAPIDJSON_PERSIST_VALUE serialize(RAPIDJSON_PERSIST_DOCUMENT &doc) const | |
{ | |
auto json = RAPIDJSON_PERSIST_VALUE(Type::kObjectType); | |
for (const auto &member : serializable::members) { | |
json.AddMember(StringRef(member.name), member.serialize(*static_cast<const T *>(this), doc), | |
doc.GetAllocator()); | |
} | |
return json; | |
} | |
bool deserialize(const RAPIDJSON_PERSIST_VALUE &json, const RAPIDJSON_PERSIST_DOCUMENT &doc, | |
NamedObjects<Encoding> &objects) | |
{ | |
if (!json.IsObject()) { | |
return false; | |
} | |
auto success = true; | |
for (const auto &member : serializable::members) { | |
const auto member_entry = json.FindMember(member.name); | |
success &= (member_entry != json.MemberEnd() && | |
member.deserialize(member_entry->value, *static_cast<T *>(this), doc, objects)) || | |
!member.checked; | |
} | |
return success; | |
} | |
friend class serializer<T>; | |
public: | |
template <typename U> | |
static bool register_member(std::function<U &(T &)> member, const Char *name, bool checked) | |
{ | |
members.emplace_back(name, checked, | |
[member](const T &instance, RAPIDJSON_PERSIST_DOCUMENT &doc) -> RAPIDJSON_PERSIST_VALUE { | |
return serializer<U>::serialize(member(const_cast<T &>(instance)), doc); | |
}, | |
[member](const RAPIDJSON_PERSIST_VALUE &json, T &instance, | |
const RAPIDJSON_PERSIST_DOCUMENT &doc, NamedObjects<Encoding> &objects) -> bool { | |
return serializer<U>::deserialize(json, member(const_cast<T &>(instance)), doc, | |
objects); | |
}); | |
return true; | |
} | |
RAPIDJSON_PERSIST_DOCUMENT serialize() const | |
{ | |
auto doc = RAPIDJSON_PERSIST_DOCUMENT(Type::kObjectType); | |
auto main = serialize(doc); | |
if (doc.MemberCount() == 0) { | |
doc.SetObject() = main.Move(); | |
} else { | |
doc.AddMember(StringRef(literals<Char>::main.data(), literals<Char>::main.length()), main, | |
doc.GetAllocator()); | |
} | |
return doc; | |
} | |
static std::optional<T> deserialize(const RAPIDJSON_PERSIST_DOCUMENT &doc) | |
{ | |
if (!doc.IsObject()) { | |
return std::nullopt; | |
} | |
auto value = std::make_optional<T>(); | |
auto objects = NamedObjects<Encoding>(); | |
const auto main_entry = doc.FindMember(StringRef(literals<Char>::main.data(), literals<Char>::main.length())); | |
if (!value->deserialize(main_entry == doc.MemberEnd() ? doc : main_entry->value, doc, objects)) { | |
return std::nullopt; | |
} | |
return value; | |
} | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// main macros | |
#define RAPIDJSON_PERSIST_INTERNAL(prefix, name, checked, ...) \ | |
static inline const auto _serializable_##name = serializable::register_member<__VA_ARGS__>( \ | |
[](auto &instance) -> __VA_ARGS__ & { return instance.name; }, prefix## #name, checked); \ | |
__VA_ARGS__ name | |
#define RAPIDJSON_PERSIST_(prefix, name, ...) RAPIDJSON_PERSIST_INTERNAL(prefix, name, false, __VA_ARGS__) | |
#define RAPIDJSON_PERSIST_A(name, ...) RAPIDJSON_PERSIST_(, name, __VA_ARGS__) | |
#define RAPIDJSON_PERSIST_W(name, ...) RAPIDJSON_PERSIST_(L, name, __VA_ARGS__) | |
#if RAPIDJSON_PERSIST_UNICODE | |
#define RAPIDJSON_PERSIST RAPIDJSON_PERSIST_W | |
#else | |
#define RAPIDJSON_PERSIST RAPIDJSON_PERSIST_A | |
#endif | |
#define RAPIDJSON_PERSIST_CHECKED_(prefix, name, ...) RAPIDJSON_PERSIST_INTERNAL(prefix, name, true, __VA_ARGS__) | |
#define RAPIDJSON_PERSIST_CHECKED_A(name, ...) RAPIDJSON_PERSIST_CHECKED_(, name, __VA_ARGS__) | |
#define RAPIDJSON_PERSIST_CHECKED_W(name, ...) RAPIDJSON_PERSIST_CHECKED_(L, name, __VA_ARGS__) | |
#if RAPIDJSON_PERSIST_UNICODE | |
#define RAPIDJSON_PERSIST_CHECKED RAPIDJSON_PERSIST_CHECKED_W | |
#else | |
#define RAPIDJSON_PERSIST_CHECKED RAPIDJSON_PERSIST_CHECKED_A | |
#endif | |
} // namespace persist | |
RAPIDJSON_NAMESPACE_END | |
#endif // RAPIDJSON_PERSIST_H_ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment