libcode-json/code/json/marshaling.hxx

786 lines
16 KiB
C++

#ifndef code__json__marshaling_hxx_
#define code__json__marshaling_hxx_
#include <code/json/optional.hxx>
#include <code/json/pointer.hxx>
#include <code/json/traits.hxx>
#include <code/json/variant.hxx>
#include <chrono>
#include <ctime>
#include <deque>
#include <iomanip>
#include <list>
#include <locale>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
namespace code::json
{
class marshaling_context_t
{
protected:
virtual ~marshaling_context_t() = default;
};
template<typename T>
struct marshaling_traits;
template<std::size_t N>
struct member_name_t
{
constexpr member_name_t(char const (&str)[N])
{
std::copy_n(str, N, name);
}
std::string const
str() const
{
return name;
}
operator std::string const() const
{
return str();
}
char name[N];
};
template<typename>
struct member_traits;
template<typename T, typename M>
struct member_traits<M T::*>
{
using class_type = T;
using member_type = M;
};
template<member_name_t Name, auto Member>
struct member_t
{
using T = member_traits<decltype(Member)>::class_type;
using M = member_traits<decltype(Member)>::member_type;
static constexpr bool is_optional{
is_optional_v<M>
};
static
void
marshal(variant& v, T const& instance, marshaling_context_t* context)
{
v.set(Name, marshaling_traits<M>::marshal(instance.*Member, context));
}
static
void
unmarshal(T& instance, variant const& v, marshaling_context_t* context)
{
if (!v.contains(Name.str())) {
if (is_optional) {
return;
}
throw std::runtime_error{"missing field '" + Name.str() + "'"};
}
instance.*Member = marshaling_traits<M>::unmarshal(v.get(Name), context);
}
struct pointer_t
{
using T = member_traits<decltype(Member)>::class_type;
using M = member_traits<decltype(Member)>::member_type;
static pointer const&
ptr()
{
static pointer ptr{Name};
return ptr;
}
static
void
marshal(variant& v, T const& instance, marshaling_context_t* context)
{
ptr().write(v, marshaling_traits<M>::marshal(instance.*Member, context));
}
static
void
unmarshal(T& instance, variant const& v, marshaling_context_t* context)
{
auto ptr_v = ptr().read(v);
if (!ptr_v) {
if (is_optional) {
return;
}
throw std::runtime_error{ "missing field '" + to_string(ptr()) + "'" };
}
instance.*Member = marshaling_traits<M>::unmarshal(*ptr_v, context);
}
};
};
template<typename First, typename... Members>
struct mapping_t
{
using T = First::T;
static
void
do_marshal(variant& v, T const& instance, marshaling_context_t* context)
{
First::marshal(v, instance, context);
((Members::marshal(v, instance, context)), ...);
}
static
variant
marshal(T const& instance, marshaling_context_t* context)
{
variant v{std::map<std::string, variant>{}};
do_marshal(v, instance, context);
return v;
}
static
void
do_unmarshal(T& instance, variant const& v, marshaling_context_t* context)
{
First::unmarshal(instance, v, context);
((Members::unmarshal(instance, v, context)), ...);
}
static
T
unmarshal(variant const& v, marshaling_context_t* context)
{
T instance;
do_unmarshal(instance, v, context);
return instance;
}
template<typename... Bases>
struct inherit_t
{
static_assert(sizeof...(Bases) > 0, "at least one base must be specified");
static
void
do_marshal(variant& v, T const& instance, marshaling_context_t* context)
{
((Bases::json::do_marshal(v, instance, context)), ...);
First::marshal(v, instance, context);
((Members::marshal(v, instance, context)), ...);
}
static
variant
marshal(T const& instance, marshaling_context_t* context)
{
variant v{std::map<std::string, variant>{}};
do_marshal(v, instance, context);
return v;
}
static
void
do_unmarshal(T& instance, variant const& v, marshaling_context_t* context)
{
((Bases::json::do_unmarshal(instance, v, context)), ...);
First::unmarshal(instance, v, context);
((Members::unmarshal(instance, v, context)), ...);
}
static
T
unmarshal(variant const& v, marshaling_context_t* context)
{
T instance;
do_unmarshal(instance, v, context);
return instance;
}
};
};
template<typename T>
struct marshaling_traits
{
static
variant
marshal(T const& instance, marshaling_context_t* context)
{
return T::json::marshal(instance, context);
}
static
T
unmarshal(variant const& v, marshaling_context_t* context)
{
return T::json::unmarshal(v, context);
}
};
template<typename T>
struct marshaling_traits<optional<T>>
{
static
variant
marshal(optional<T> const& model, marshaling_context_t* context)
{
if (model) {
return marshaling_traits<T>::marshal(*model, context);
}
return {};
}
static optional<T>
unmarshal(variant const& value, marshaling_context_t* context)
{
if (value.is_undefined()) {
return {};
}
return marshaling_traits<T>::unmarshal(value, context);
}
};
template<typename... T>
struct marshaling_traits<std::variant<T...>>
{
static
variant
marshal(std::variant<T...> const& model, marshaling_context_t* context)
{
variant result;
auto visitor = [&result, context](const auto& obj)
{
using type = std::decay_t<decltype(obj)>;
result = marshaling_traits<type>::marshal(obj, context);
result.set("$type", std::decay_t<decltype(obj)>::type_identifier);
};
std::visit(visitor, model);
return result;
}
template<typename Current, typename... Next>
struct unmarshaler
{
static
std::variant<T...>
unmarshal(variant const& value, marshaling_context_t* context)
{
if (!value.contains("$type"))
throw std::invalid_argument{"variant missing '$type' identifier"};
if (value.get("$type").get_string() == Current::type_identifier)
return marshaling_traits<Current>::unmarshal(value, context);
if constexpr (sizeof...(Next)> 0)
return unmarshaler<Next...>::unmarshal(value, context);
throw std::invalid_argument{"couldn't unmarshal value"};
}
};
static
std::variant<T...>
unmarshal(variant const& value, marshaling_context_t* context)
{
return unmarshaler<T...>::unmarshal(value, context);
}
};
template<typename T>
struct marshaling_traits<std::vector<T>>
{
static
variant
marshal(std::vector<T> const& model, marshaling_context_t* context)
{
std::vector<variant> a;
for (auto const& j : model)
a.emplace_back(marshaling_traits<T>::marshal(j, context));
return a;
}
static
std::vector<T>
unmarshal(variant const& value, marshaling_context_t* context)
{
if (!value.is_array())
throw std::runtime_error{"not an array"};
std::vector<T> model;
for (auto const& j : value)
model.emplace_back(marshaling_traits<T>::unmarshal(j, context));
return model;
}
};
template<typename T>
struct marshaling_traits<std::list<T>>
{
static
variant
marshal(std::list<T> const& model, marshaling_context_t* context)
{
std::vector<variant> a;
for (auto const& j : model)
a.emplace_back(marshaling_traits<T>::marshal(j, context));
return a;
}
static
std::list<T>
unmarshal(variant const& value, marshaling_context_t* context)
{
if (!value.is_array())
throw std::runtime_error{"not an array"};
std::list<T> model;
for (auto const& j : value)
model.emplace_back(marshaling_traits<T>::unmarshal(j, context));
return model;
}
};
template<typename T>
struct marshaling_traits<std::deque<T>>
{
static
variant
marshal(std::deque<T> const& model, marshaling_context_t* context)
{
std::vector<variant> a;
for (auto const& j : model)
a.emplace_back(marshaling_traits<T>::marshal(j, context));
return a;
}
static
std::deque<T>
unmarshal(variant const& value, marshaling_context_t* context)
{
if (!value.is_array())
throw std::runtime_error{"not an array"};
std::deque<T> model;
for (auto const& j : value)
model.emplace_back(marshaling_traits<T>::unmarshal(j, context));
return model;
}
};
template<typename T>
struct marshaling_traits<std::set<T>>
{
static
variant
marshal(std::set<T> const& model, marshaling_context_t* context)
{
std::vector<variant> a;
for (auto const& j : model)
a.emplace_back(marshaling_traits<T>::marshal(j, context));
return a;
}
static
std::set<T>
unmarshal(variant const& value, marshaling_context_t* context)
{
if (!value.is_array())
throw std::runtime_error{"not an array"};
std::set<T> model;
for (auto const& j : value)
model.emplace(marshaling_traits<T>::unmarshal(j, context));
return model;
}
};
template<>
struct marshaling_traits<bool>
{
static
variant
marshal(bool model, marshaling_context_t*)
{
return model;
}
static
bool
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_boolean())
throw std::runtime_error{"not a boolean"};
return value.get_boolean();
}
};
template<>
struct marshaling_traits<short int> {
static
variant
marshal(short int model, marshaling_context_t*)
{
return model;
}
static
short int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<short int>();
}
};
template<>
struct marshaling_traits<int>
{
static
variant
marshal(int model, marshaling_context_t*)
{
return model;
}
static
int
unmarshal(variant const& v, marshaling_context_t*)
{
if (!v.is_number()) {
throw std::runtime_error{"not a number"};
}
return v.get_number<int>();
}
};
template<>
struct marshaling_traits<long int> {
static
variant
marshal(long int model, marshaling_context_t*)
{
return model;
}
static
long int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<long int>();
}
};
template<>
struct marshaling_traits<long long int> {
static
variant
marshal(long long int model, marshaling_context_t*)
{
return model;
}
static
long long int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<long long int>();
}
};
template<>
struct marshaling_traits<unsigned short int> {
static
variant
marshal(unsigned short int model, marshaling_context_t*)
{
return model;
}
static
unsigned short int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<unsigned short int>();
}
};
template<>
struct marshaling_traits<unsigned int> {
static
variant
marshal(unsigned int model, marshaling_context_t*)
{
return model;
}
static
unsigned int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<unsigned int>();
}
};
template<>
struct marshaling_traits<unsigned long int> {
static
variant
marshal(unsigned long int model, marshaling_context_t*)
{
return model;
}
static
unsigned long int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<unsigned long int>();
}
};
// unsigned long long int
template<>
struct marshaling_traits<unsigned long long int> {
static
variant
marshal(unsigned long long int model, marshaling_context_t*)
{
return model;
}
static
unsigned long long int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<unsigned long long int>();
}
};
template<>
struct marshaling_traits<float> {
static
variant
marshal(float model, marshaling_context_t*)
{
return model;
}
static
float
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<float>();
}
};
template<>
struct marshaling_traits<double> {
static
variant
marshal(double model, marshaling_context_t*)
{
return model;
}
static
double
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<double>();
}
};
template<>
struct marshaling_traits<long double> {
static
variant
marshal(long double model, marshaling_context_t*)
{
return model;
}
static
long double
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<long double>();
}
};
template<>
struct marshaling_traits<std::string>
{
static
variant
marshal(std::string const& model, marshaling_context_t*)
{
return model;
}
static
std::string
unmarshal(variant const& v, marshaling_context_t*)
{
if (!v.is_string()) {
throw std::runtime_error{"not a string"};
}
return v.get_string();
}
};
template<>
struct marshaling_traits<std::chrono::system_clock::time_point>
{
using model_type = std::string;
static constexpr const char time_format[] = "%a, %d %b %Y %H:%M:%S GMT";
static
variant
marshal(std::chrono::system_clock::time_point const& model, marshaling_context_t*)
{
std::time_t now_c = std::chrono::system_clock::to_time_t(model);
struct std::tm tm_buf;
std::stringstream str;
str.imbue(std::locale{});
#ifdef _MSC_VER
::gmtime_s(&tm_buf, &now_c); // Stupid Microsoft.
str << std::put_time(&tm_buf, time_format);
#else
::gmtime_r(&now_c, &tm_buf);
str << std::put_time(&tm_buf, time_format);
#endif
return str.str();
}
static
std::chrono::system_clock::time_point
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_string())
throw std::runtime_error{ "not a string" };
std::tm tm{};
std::istringstream str{ value.get_string() };
str.imbue(std::locale{});
str >> std::get_time(&tm, time_format);
if (str.fail())
return {};
#ifdef _MSC_VER
auto localtime = _mkgmtime(&tm);
return std::chrono::system_clock::from_time_t(localtime);
#else
auto localtime = timegm(&tm);
return std::chrono::system_clock::from_time_t(localtime);
#endif
}
};
template<typename T>
variant
marshal(T const& model, marshaling_context_t* context = nullptr)
{
return marshaling_traits<T>::marshal(model, context);
}
template<typename T>
T
unmarshal(variant const& v, marshaling_context_t* context = nullptr)
{
return marshaling_traits<T>::unmarshal(v, context);
}
} // namespace code::json
#endif