Reimplement marshaling
Reimplement marshaling using compile-time templates. Closes #1
This commit is contained in:
parent
53255d1e23
commit
fd27637d3d
699
code/json/marshaling.hxx
Normal file
699
code/json/marshaling.hxx
Normal file
@ -0,0 +1,699 @@
|
||||
#ifndef code__json__marshaling_hxx_
|
||||
#define code__json__marshaling_hxx_
|
||||
|
||||
#include <code/json/optional.hxx>
|
||||
#include <code/json/pointer.hxx>
|
||||
#include <code/json/variant.hxx>
|
||||
#include <code/json/write.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);
|
||||
}
|
||||
|
||||
operator std::string const() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
{
|
||||
instance.*Member = marshaling_traits<M>::unmarshal(v.get(Name), context);
|
||||
}
|
||||
|
||||
struct pointer_t
|
||||
{
|
||||
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) {
|
||||
throw std::runtime_error{ "missing field '" + to_string(ptr()) + "'" };
|
||||
}
|
||||
|
||||
instance.*Member = marshaling_traits<M>::unmarshal(*ptr_v, context);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
template<typename T, typename... Members>
|
||||
struct mapping_t
|
||||
{
|
||||
static
|
||||
variant
|
||||
marshal(T const& instance, marshaling_context_t* context)
|
||||
{
|
||||
variant v{std::map<std::string, variant>{}};
|
||||
((Members::marshal(v, instance, context)), ...);
|
||||
return v;
|
||||
}
|
||||
|
||||
static
|
||||
T
|
||||
unmarshal(variant const& v, marshaling_context_t* context)
|
||||
{
|
||||
T instance;
|
||||
((Members::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
|
@ -1,6 +1,6 @@
|
||||
#include <code/json/marshaling.hxx>
|
||||
#include <code/json/optional.hxx>
|
||||
|
||||
#include <code/json/marshaling/marshaling-traits.hxx>
|
||||
#include <code/json/serialize.hxx>
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
@ -14,12 +14,31 @@
|
||||
#define TEST_FALSE(x) if ((x)) return __LINE__;
|
||||
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
|
||||
|
||||
using code::json::mapping_t;
|
||||
using code::json::marshal;
|
||||
using code::json::marshaling_traits;
|
||||
using code::json::member_t;
|
||||
using code::json::unmarshal;
|
||||
|
||||
struct name_t
|
||||
{
|
||||
std::string first;
|
||||
std::string last;
|
||||
|
||||
using json = mapping_t<
|
||||
name_t,
|
||||
member_t<"/person/first", &name_t::first>::pointer_t,
|
||||
member_t<"/person/last", &name_t::last>::pointer_t
|
||||
>;
|
||||
|
||||
};
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
DEFINE_TEST("optional<int>{}")
|
||||
{
|
||||
using traits_type = code::json::marshaling::marshaling_traits< code::json::optional< int > >;
|
||||
using traits_type = marshaling_traits<code::json::optional<int>>;
|
||||
|
||||
std::optional<int> model;
|
||||
|
||||
@ -30,7 +49,7 @@ main()
|
||||
|
||||
DEFINE_TEST("optional<int>{0}")
|
||||
{
|
||||
using traits_type = code::json::marshaling::marshaling_traits< code::json::optional< int > >;
|
||||
using traits_type = marshaling_traits<code::json::optional<int>>;
|
||||
|
||||
std::optional<int> model{0};
|
||||
|
||||
@ -42,7 +61,7 @@ main()
|
||||
|
||||
DEFINE_TEST("string")
|
||||
{
|
||||
using traits_type = code::json::marshaling::marshaling_traits< std::string >;
|
||||
using traits_type = marshaling_traits<std::string>;
|
||||
|
||||
std::string model{"hello, world"};
|
||||
|
||||
@ -54,7 +73,7 @@ main()
|
||||
|
||||
DEFINE_TEST("vector<int>{}")
|
||||
{
|
||||
using traits_type = code::json::marshaling::marshaling_traits< std::vector< int > >;
|
||||
using traits_type = marshaling_traits<std::vector<int>>;
|
||||
|
||||
std::vector<int> model1;
|
||||
|
||||
@ -69,7 +88,7 @@ main()
|
||||
|
||||
DEFINE_TEST("vector<int>{...}")
|
||||
{
|
||||
using traits_type = code::json::marshaling::marshaling_traits< std::vector< int > >;
|
||||
using traits_type = marshaling_traits<std::vector<int>>;
|
||||
|
||||
std::vector<int> model1{ 1, 2, 3, 4 };
|
||||
|
||||
@ -84,7 +103,7 @@ main()
|
||||
|
||||
DEFINE_TEST("list<int>{}")
|
||||
{
|
||||
using traits_type = code::json::marshaling::marshaling_traits< std::list< int > >;
|
||||
using traits_type = marshaling_traits<std::list<int>>;
|
||||
|
||||
std::list<int> model1;
|
||||
|
||||
@ -99,7 +118,7 @@ main()
|
||||
|
||||
DEFINE_TEST("list<int>{...}")
|
||||
{
|
||||
using traits_type = code::json::marshaling::marshaling_traits< std::list< int > >;
|
||||
using traits_type = marshaling_traits<std::list<int>>;
|
||||
|
||||
std::list<int> model1{ 1, 2, 3, 4 };
|
||||
|
||||
@ -114,7 +133,7 @@ main()
|
||||
|
||||
DEFINE_TEST("deque<int>{}")
|
||||
{
|
||||
using traits_type = code::json::marshaling::marshaling_traits< std::deque< int > >;
|
||||
using traits_type = marshaling_traits<std::deque<int>>;
|
||||
|
||||
std::deque<int> model1;
|
||||
|
||||
@ -129,7 +148,7 @@ main()
|
||||
|
||||
DEFINE_TEST("deque<int>{...}")
|
||||
{
|
||||
using traits_type = code::json::marshaling::marshaling_traits< std::deque< int > >;
|
||||
using traits_type = marshaling_traits<std::deque<int>>;
|
||||
|
||||
std::deque<int> model1{ 1, 2, 3, 4 };
|
||||
|
||||
@ -142,5 +161,15 @@ main()
|
||||
TEST_EQUAL(model1, model2);
|
||||
}
|
||||
|
||||
DEFINE_TEST("pointer")
|
||||
{
|
||||
name_t name1{"Jane", "Doe"};
|
||||
auto v = marshal<name_t>(name1);
|
||||
auto name2 = unmarshal<name_t>(v);
|
||||
|
||||
TEST_EQUAL(name1.first, name2.first);
|
||||
TEST_EQUAL(name1.last, name2.last);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,245 +0,0 @@
|
||||
#ifndef json__marshaling__mapping_hxx_
|
||||
#define json__marshaling__mapping_hxx_
|
||||
|
||||
#include <code/json/optional.hxx>
|
||||
#include <code/json/pointer.hxx>
|
||||
#include <code/json/variant.hxx>
|
||||
|
||||
#include <code/json/marshaling/marshaling-traits.hxx>
|
||||
#include <code/json/marshaling/traits.hxx>
|
||||
|
||||
#include <functional>
|
||||
#include <variant>
|
||||
|
||||
namespace code::json::marshaling {
|
||||
|
||||
template< typename T >
|
||||
class member_mapping {
|
||||
public:
|
||||
using getter_type = std::function< variant(T const&, marshaling_context*) >;
|
||||
using setter_type = std::function< void(T&, variant const&, marshaling_context*) >;
|
||||
|
||||
member_mapping(std::string key,
|
||||
bool optional,
|
||||
getter_type getter,
|
||||
setter_type setter)
|
||||
: key_{ std::move(key) },
|
||||
optional_{ optional },
|
||||
getter_{ std::move(getter) },
|
||||
setter_{ std::move(setter) }
|
||||
{}
|
||||
|
||||
member_mapping(pointer p,
|
||||
bool optional,
|
||||
getter_type getter,
|
||||
setter_type setter)
|
||||
: key_{ std::move(p) },
|
||||
optional_{ optional },
|
||||
getter_{ std::move(getter) },
|
||||
setter_{ std::move(setter) }
|
||||
{}
|
||||
|
||||
template< typename U >
|
||||
member_mapping(member_mapping< U > const& other)
|
||||
: key_{other.key_},
|
||||
optional_{other.optional_},
|
||||
getter_{other.getter_},
|
||||
setter_{other.setter_}
|
||||
{}
|
||||
|
||||
std::variant<std::string, pointer> const&
|
||||
key() const
|
||||
{
|
||||
return key_;
|
||||
}
|
||||
|
||||
bool
|
||||
optional() const
|
||||
{
|
||||
return optional_;
|
||||
}
|
||||
|
||||
variant
|
||||
get(T const& instance, marshaling_context* context) const
|
||||
{
|
||||
return getter_(instance, context);
|
||||
}
|
||||
|
||||
void
|
||||
set(T& instance, variant const& value, marshaling_context* context) const
|
||||
{
|
||||
setter_(instance, value, context);
|
||||
}
|
||||
|
||||
private:
|
||||
template< typename U >
|
||||
friend class member_mapping;
|
||||
|
||||
std::variant<std::string, pointer> key_;
|
||||
bool optional_;
|
||||
getter_type getter_;
|
||||
setter_type setter_;
|
||||
};
|
||||
|
||||
template< typename T, typename M >
|
||||
member_mapping< T >
|
||||
member(std::string key, M T::*member)
|
||||
{
|
||||
return member_mapping< T >{
|
||||
std::move(key),
|
||||
is_optional_v<M>,
|
||||
[member](T const& instance, marshaling_context* context) -> json::variant
|
||||
{
|
||||
return marshaling_traits< M >::marshal(instance.*member, context);
|
||||
},
|
||||
[member](T& instance, json::variant const& v, marshaling_context* context)
|
||||
{
|
||||
instance.*member = marshaling_traits< M >::unmarshal(v, context);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template< typename T, typename M >
|
||||
member_mapping< T >
|
||||
member(pointer p, M T::*member)
|
||||
{
|
||||
return member_mapping< T >{
|
||||
std::move(p),
|
||||
is_optional_v<M>,
|
||||
[member](T const& instance, marshaling_context* context) -> json::variant
|
||||
{
|
||||
return marshaling_traits< M >::marshal(instance.*member, context);
|
||||
},
|
||||
[member](T& instance, json::variant const& v, marshaling_context* context)
|
||||
{
|
||||
instance.*member = marshaling_traits< M >::unmarshal(v, context);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template< typename T, typename Accessor >
|
||||
member_mapping< T >
|
||||
accessor(std::string key, Accessor access)
|
||||
{
|
||||
return member_mapping< T >{
|
||||
std::move(key),
|
||||
is_optional_v<std::decay_t<decltype(access(std::declval<T>()))>>,
|
||||
[access](T const& instance, marshaling_context* context) -> json::variant
|
||||
{
|
||||
using M = std::decay_t<decltype(access(instance))>;
|
||||
return marshaling_traits< M >::marshal(access(instance), context);
|
||||
},
|
||||
[access](T& instance, json::variant const& v, marshaling_context* context)
|
||||
{
|
||||
using M = std::decay_t<decltype(access(instance))>;
|
||||
access(instance) = marshaling_traits< M >::unmarshal(v, context);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template< typename T, typename Accessor >
|
||||
member_mapping< T >
|
||||
accessor(pointer p, Accessor access)
|
||||
{
|
||||
return member_mapping< T >{
|
||||
std::move(p),
|
||||
is_optional_v<std::decay_t<decltype(access(std::declval<T>()))>>,
|
||||
[access](T const& instance, marshaling_context* context) -> json::variant
|
||||
{
|
||||
using M = std::decay_t<decltype(access(instance))>;
|
||||
return marshaling_traits< M >::marshal(access(instance), context);
|
||||
},
|
||||
[access](T& instance, json::variant const& v, marshaling_context* context)
|
||||
{
|
||||
using M = std::decay_t<decltype(access(instance))>;
|
||||
access(instance) = marshaling_traits< M >::unmarshal(v, context);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template< typename T, typename V >
|
||||
member_mapping< T >
|
||||
static_value(std::string key, V value)
|
||||
{
|
||||
return member_mapping< T >{
|
||||
std::move(key),
|
||||
true,
|
||||
[value](T const& instance, marshaling_context* context) -> json::variant {
|
||||
return marshaling_traits< V >::marshal(value, context);
|
||||
},
|
||||
[](T&, json::variant const&, marshaling_context*) {
|
||||
// No-op.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template< typename T, typename V >
|
||||
member_mapping< T >
|
||||
static_value(pointer p, V value)
|
||||
{
|
||||
return member_mapping< T >{
|
||||
std::move(p),
|
||||
true,
|
||||
[value](T const& instance, marshaling_context* context) -> json::variant {
|
||||
return marshaling_traits< V >::marshal(value, context);
|
||||
},
|
||||
[](T&, json::variant const&, marshaling_context*) {
|
||||
// No-op.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template< typename T >
|
||||
class mapping {
|
||||
public:
|
||||
using container_type = std::vector< member_mapping< T > >;
|
||||
using const_iterator = typename container_type::const_iterator;
|
||||
|
||||
mapping(container_type mappings)
|
||||
: mappings_{std::move(mappings)}
|
||||
{}
|
||||
|
||||
mapping(std::initializer_list< member_mapping< T > > init)
|
||||
{
|
||||
for (auto const& j : init)
|
||||
mappings_.emplace_back(j);
|
||||
}
|
||||
|
||||
const_iterator
|
||||
begin() const
|
||||
{
|
||||
return mappings_.begin();
|
||||
}
|
||||
|
||||
const_iterator
|
||||
end() const
|
||||
{
|
||||
return mappings_.end();
|
||||
}
|
||||
|
||||
private:
|
||||
container_type mappings_;
|
||||
|
||||
};
|
||||
|
||||
template<typename T, typename... Bases>
|
||||
mapping<T>
|
||||
map(std::initializer_list<member_mapping<T>> init)
|
||||
{
|
||||
typename mapping<T>::container_type m{init};
|
||||
|
||||
auto inherit = [&](auto&& base)
|
||||
{
|
||||
for (auto const& j : base) {
|
||||
m.emplace_back(j);
|
||||
}
|
||||
};
|
||||
|
||||
((inherit(Bases::json())), ...);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
} // namespace code::json::marshaling
|
||||
|
||||
#endif
|
@ -1,8 +0,0 @@
|
||||
#include <code/json/marshaling/marshaling-context.hxx>
|
||||
|
||||
namespace code::json::marshaling {
|
||||
|
||||
marshaling_context::~marshaling_context()
|
||||
{}
|
||||
|
||||
} // namespace code::json::marshaling
|
@ -1,13 +0,0 @@
|
||||
#ifndef code__json__marshaling__marshaling_context_hxx_
|
||||
#define code__json__marshaling__marshaling_context_hxx_
|
||||
|
||||
namespace code::json::marshaling {
|
||||
|
||||
class marshaling_context {
|
||||
protected:
|
||||
virtual ~marshaling_context();
|
||||
};
|
||||
|
||||
} // namespace code::json::marshaling
|
||||
|
||||
#endif
|
@ -1,626 +0,0 @@
|
||||
#ifndef code__json__marshaling__marshaling_traits_hxx_
|
||||
#define code__json__marshaling__marshaling_traits_hxx_
|
||||
|
||||
#include <code/json/optional.hxx>
|
||||
#include <code/json/pointer.hxx>
|
||||
#include <code/json/variant.hxx>
|
||||
|
||||
#include <code/json/marshaling/marshaling-context.hxx>
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <deque>
|
||||
#include <iomanip>
|
||||
#include <iostream> // TODO: Remove.
|
||||
#include <list>
|
||||
#include <locale>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
namespace code::json::marshaling {
|
||||
|
||||
template< typename T >
|
||||
struct marshaling_traits {
|
||||
using model_type = T;
|
||||
|
||||
static variant
|
||||
marshal(model_type const& model, marshaling_context* context)
|
||||
{
|
||||
variant v{std::map<std::string, variant>{}};
|
||||
|
||||
// TODO: Handle mappings with pointers.
|
||||
for (auto const& mapping : model_type::json()) {
|
||||
if (std::holds_alternative<pointer>(mapping.key())) {
|
||||
auto ptr = std::get<pointer>(mapping.key());
|
||||
ptr.write(v, mapping.get(model, context));
|
||||
}
|
||||
else {
|
||||
v.set(std::get<std::string>(mapping.key()), mapping.get(model, context));
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
static model_type
|
||||
unmarshal(variant const& value, marshaling_context* context)
|
||||
{
|
||||
if constexpr (std::is_default_constructible_v< model_type >) {
|
||||
// TODO: Change exception type.
|
||||
if (!value.is_object())
|
||||
throw std::runtime_error{ "cannot unmarshal non-object value" };
|
||||
|
||||
model_type model;
|
||||
for (auto const& mapping : model_type::json()) {
|
||||
if (std::holds_alternative<pointer>(mapping.key())) {
|
||||
auto ptr = std::get<pointer>(mapping.key());
|
||||
auto v = ptr.read(value);
|
||||
|
||||
if (!v && !mapping.optional())
|
||||
// FIXME: Add ptr to exception.
|
||||
throw std::runtime_error{ "missing field '" + to_string(ptr) + "'" };
|
||||
else
|
||||
mapping.set(model, *v, context);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& key = std::get<std::string>(mapping.key());
|
||||
|
||||
if (!value.contains(key)) {
|
||||
if (!mapping.optional())
|
||||
// TODO: Change exception type.
|
||||
throw std::runtime_error{ "missing field '" + key + "'" };
|
||||
|
||||
continue; // Try next key.
|
||||
}
|
||||
|
||||
mapping.set(model, value.get(key), context);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error{ "this shouldn't compile" };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template< typename T >
|
||||
struct marshaling_traits< optional< T > > {
|
||||
using model_type = optional< T >;
|
||||
|
||||
static variant
|
||||
marshal(optional< T > const& model, marshaling_context* context)
|
||||
{
|
||||
if (model)
|
||||
return marshaling_traits< T >::marshal(*model, context);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static optional< T >
|
||||
unmarshal(variant const& value, marshaling_context* context)
|
||||
{
|
||||
if (value.is_undefined())
|
||||
return {};
|
||||
|
||||
return marshaling_traits< T >::unmarshal(value, context);
|
||||
}
|
||||
};
|
||||
|
||||
template< typename... T >
|
||||
struct marshaling_traits< std::variant< T... > > {
|
||||
using model_type = std::variant< T... >;
|
||||
|
||||
static variant
|
||||
marshal(std::variant< T... > const& model, marshaling_context* 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* 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* context)
|
||||
{
|
||||
return unmarshaler<T...>::unmarshal(value, context);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct marshaling_traits< bool > {
|
||||
using model_type = bool;
|
||||
|
||||
static variant
|
||||
marshal(bool model, marshaling_context*)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
static bool
|
||||
unmarshal(variant const& value, marshaling_context*)
|
||||
{
|
||||
if (!value.is_boolean())
|
||||
throw std::runtime_error{ "not a boolean" };
|
||||
|
||||
return value.get_boolean();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct marshaling_traits< short int > {
|
||||
using model_type = short int;
|
||||
|
||||
static variant
|
||||
marshal(short int model, marshaling_context*)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
static short int
|
||||
unmarshal(variant const& value, marshaling_context*)
|
||||
{
|
||||
if (!value.is_number())
|
||||
throw std::runtime_error{ "not a number" };
|
||||
|
||||
return value.get_number< short int >();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct marshaling_traits< int > {
|
||||
using model_type = int;
|
||||
|
||||
static variant
|
||||
marshal(int model, marshaling_context*)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
static int
|
||||
unmarshal(variant const& value, marshaling_context*)
|
||||
{
|
||||
if (!value.is_number())
|
||||
throw std::runtime_error{ "not a number" };
|
||||
|
||||
return value.get_number< int >();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct marshaling_traits< long int > {
|
||||
using model_type = long int;
|
||||
|
||||
static variant
|
||||
marshal(long int model, marshaling_context*)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
static long int
|
||||
unmarshal(variant const& value, marshaling_context*)
|
||||
{
|
||||
if (!value.is_number())
|
||||
throw std::runtime_error{ "not a number" };
|
||||
|
||||
return value.get_number< long int >();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct marshaling_traits< long long int > {
|
||||
using model_type = long long int;
|
||||
|
||||
static variant
|
||||
marshal(long long int model, marshaling_context*)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
static long long int
|
||||
unmarshal(variant const& value, marshaling_context*)
|
||||
{
|
||||
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 > {
|
||||
using model_type = unsigned short int;
|
||||
|
||||
static variant
|
||||
marshal(unsigned short int model, marshaling_context*)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
static unsigned short int
|
||||
unmarshal(variant const& value, marshaling_context*)
|
||||
{
|
||||
if (!value.is_number())
|
||||
throw std::runtime_error{ "not a number" };
|
||||
|
||||
return value.get_number< unsigned short int >();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct marshaling_traits< unsigned int > {
|
||||
using model_type = unsigned int;
|
||||
|
||||
static variant
|
||||
marshal(unsigned int model, marshaling_context*)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
unmarshal(variant const& value, marshaling_context*)
|
||||
{
|
||||
if (!value.is_number())
|
||||
throw std::runtime_error{ "not a number" };
|
||||
|
||||
return value.get_number< unsigned int >();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct marshaling_traits< unsigned long int > {
|
||||
using model_type = unsigned long int;
|
||||
|
||||
static variant
|
||||
marshal(unsigned long int model, marshaling_context*)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
static unsigned long int
|
||||
unmarshal(variant const& value, marshaling_context*)
|
||||
{
|
||||
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 > {
|
||||
using model_type = unsigned long long int;
|
||||
|
||||
static variant
|
||||
marshal(unsigned long long int model, marshaling_context*)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
static unsigned long long int
|
||||
unmarshal(variant const& value, marshaling_context*)
|
||||
{
|
||||
if (!value.is_number())
|
||||
throw std::runtime_error{ "not a number" };
|
||||
|
||||
return value.get_number< unsigned long long int >();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct marshaling_traits< float > {
|
||||
using model_type = float;
|
||||
|
||||
static variant
|
||||
marshal(float model, marshaling_context*)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
static float
|
||||
unmarshal(variant const& value, marshaling_context*)
|
||||
{
|
||||
if (!value.is_number())
|
||||
throw std::runtime_error{ "not a number" };
|
||||
|
||||
return value.get_number< float >();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct marshaling_traits< double > {
|
||||
using model_type = double;
|
||||
|
||||
static variant
|
||||
marshal(double model, marshaling_context*)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
static double
|
||||
unmarshal(variant const& value, marshaling_context*)
|
||||
{
|
||||
if (!value.is_number())
|
||||
throw std::runtime_error{ "not a number" };
|
||||
|
||||
return value.get_number< double >();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct marshaling_traits< long double > {
|
||||
using model_type = long double;
|
||||
|
||||
static variant
|
||||
marshal(long double model, marshaling_context*)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
static long double
|
||||
unmarshal(variant const& value, marshaling_context*)
|
||||
{
|
||||
if (!value.is_number())
|
||||
throw std::runtime_error{ "not a number" };
|
||||
|
||||
return value.get_number< long double >();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct marshaling_traits< std::string > {
|
||||
using model_type = std::string;
|
||||
|
||||
static variant
|
||||
marshal(std::string const& model, marshaling_context*)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
static std::string
|
||||
unmarshal(variant const& value, marshaling_context*)
|
||||
{
|
||||
if (!value.is_string())
|
||||
throw std::runtime_error{ "not a string" };
|
||||
|
||||
return value.get_string();
|
||||
}
|
||||
};
|
||||
|
||||
template< typename T >
|
||||
struct marshaling_traits< std::vector< T > > {
|
||||
using model_type = std::vector< T >;
|
||||
|
||||
static variant
|
||||
marshal(std::vector< T > const& model, marshaling_context* 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* 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 > > {
|
||||
using model_type = std::list< T >;
|
||||
|
||||
static variant
|
||||
marshal(std::list< T > const& model, marshaling_context* 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* 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 > > {
|
||||
using model_type = std::deque< T >;
|
||||
|
||||
static variant
|
||||
marshal(std::deque< T > const& model, marshaling_context* 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* 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 > > {
|
||||
using model_type = std::set< T >;
|
||||
|
||||
static variant
|
||||
marshal(std::set< T > const& model, marshaling_context* 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* 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< std::chrono::system_clock::time_point > {
|
||||
using model_type = std::string;
|
||||
|
||||
static variant
|
||||
marshal(std::chrono::system_clock::time_point const& model,
|
||||
marshaling_context*)
|
||||
{
|
||||
static constexpr const char time_format[] = "%a, %d %b %Y %H:%M:%S GMT";
|
||||
|
||||
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*)
|
||||
{
|
||||
static constexpr const char time_format[] = "%a, %d %b %Y %H:%M:%S GMT";
|
||||
|
||||
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 E,
|
||||
std::string(&to_string)(E const&),
|
||||
E(&from_string)(std::string const&)>
|
||||
struct enum_mapping
|
||||
{
|
||||
static
|
||||
variant
|
||||
marshal(E const& value, marshaling_context*)
|
||||
{
|
||||
return to_string(value);
|
||||
}
|
||||
|
||||
static
|
||||
E
|
||||
unmarshal(variant const& v, marshaling_context*)
|
||||
{
|
||||
return from_string(v.get_string());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace code::json::marshaling
|
||||
|
||||
#endif
|
@ -1,29 +0,0 @@
|
||||
#ifndef code__json__marshaling__marshaling_hxx_
|
||||
#define code__json__marshaling__marshaling_hxx_
|
||||
|
||||
#include <code/json/variant.hxx>
|
||||
|
||||
#include <code/json/marshaling/marshaling-context.hxx>
|
||||
#include <code/json/marshaling/marshaling-traits.hxx>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace code::json::marshaling {
|
||||
|
||||
template< typename T >
|
||||
variant
|
||||
marshal(T const& model, marshaling_context* context = nullptr)
|
||||
{
|
||||
return marshaling_traits< T >::marshal(model, context);
|
||||
}
|
||||
|
||||
template< typename T >
|
||||
typename marshaling_traits< T >::model_type
|
||||
unmarshal(variant const& value, marshaling_context* context = nullptr)
|
||||
{
|
||||
return marshaling_traits< T >::unmarshal(value, context);
|
||||
}
|
||||
|
||||
} // namespace code::json::marshaling
|
||||
|
||||
#endif
|
@ -1,91 +0,0 @@
|
||||
#ifndef code__json__marshaling__serialize_hxx_
|
||||
#define code__json__marshaling__serialize_hxx_
|
||||
|
||||
#include <code/json/marshaling/marshaling-context.hxx>
|
||||
#include <code/json/marshaling/marshaling.hxx>
|
||||
|
||||
#include <code/json/read.hxx>
|
||||
#include <code/json/write.hxx>
|
||||
|
||||
#include <istream>
|
||||
|
||||
namespace code::json::marshaling {
|
||||
|
||||
template< typename T >
|
||||
void
|
||||
serialize(std::ostream& o, T const& model)
|
||||
{
|
||||
write(o, marshal(model));
|
||||
}
|
||||
|
||||
template< typename T >
|
||||
void
|
||||
serialize(std::ostream&& o, T const& model)
|
||||
{
|
||||
serialize(o, model);
|
||||
}
|
||||
|
||||
template< typename T >
|
||||
std::string
|
||||
serialize(T const& model)
|
||||
{
|
||||
std::ostringstream str;
|
||||
serialize(str, model);
|
||||
return str.str();
|
||||
}
|
||||
|
||||
template< typename T >
|
||||
T
|
||||
deserialize(diagnostics& d,
|
||||
std::istream& i,
|
||||
marshaling_context* context = nullptr)
|
||||
{
|
||||
return unmarshal< T >(read(d, i), context);
|
||||
}
|
||||
|
||||
template< typename T >
|
||||
T
|
||||
deserialize(std::istream& i, marshaling_context* context = nullptr)
|
||||
{
|
||||
return unmarshal< T >(read(i), context);
|
||||
}
|
||||
|
||||
template< typename T >
|
||||
T
|
||||
deserialize(diagnostics& d,
|
||||
std::istream&& i,
|
||||
marshaling_context* context = nullptr)
|
||||
{
|
||||
return deserialize< T >(d, i, context);
|
||||
}
|
||||
|
||||
template< typename T >
|
||||
T
|
||||
deserialize(std::istream&& i, marshaling_context* context = nullptr)
|
||||
{
|
||||
return deserialize< T >(i, context);
|
||||
}
|
||||
|
||||
template< typename T >
|
||||
T
|
||||
deserialize(diagnostics& d,
|
||||
std::string const& str,
|
||||
marshaling_context* context = nullptr)
|
||||
{
|
||||
// TODO use std::string overload of read
|
||||
return deserialize< T >(
|
||||
d, std::istringstream{ str, std::ios::in | std::ios::binary }, context);
|
||||
}
|
||||
|
||||
template< typename T >
|
||||
T
|
||||
deserialize(std::string const& str, marshaling_context* context = nullptr)
|
||||
{
|
||||
// TODO use std::string overload of read
|
||||
return deserialize< T >(
|
||||
std::istringstream{ str, std::ios::in | std::ios::binary }, context);
|
||||
}
|
||||
|
||||
} // namespace code::json::marshaling
|
||||
|
||||
#endif
|
@ -1,60 +0,0 @@
|
||||
#include <code/json/marshaling/mapping.hxx>
|
||||
#include <code/json/marshaling/serialize.hxx>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#define DEFINE_TEST(x) std::cout << x << '\n';
|
||||
#define TEST_TRUE(x) if (!(x)) return __LINE__;
|
||||
#define TEST_FALSE(x) if ((x)) return __LINE__;
|
||||
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
|
||||
|
||||
struct person_name {
|
||||
std::string first;
|
||||
std::string last;
|
||||
|
||||
static code::json::marshaling::mapping< person_name > const&
|
||||
json()
|
||||
{
|
||||
static code::json::marshaling::mapping< person_name > const mapping{
|
||||
code::json::marshaling::member("first", &person_name::first),
|
||||
code::json::marshaling::member("last", &person_name::last)
|
||||
};
|
||||
|
||||
return mapping;
|
||||
}
|
||||
};
|
||||
|
||||
struct person {
|
||||
person_name name;
|
||||
int age;
|
||||
|
||||
static code::json::marshaling::mapping< person > const&
|
||||
json()
|
||||
{
|
||||
static code::json::marshaling::mapping< person > const mapping{
|
||||
code::json::marshaling::member("person", &person::name),
|
||||
code::json::marshaling::member("age", &person::age)
|
||||
};
|
||||
|
||||
return mapping;
|
||||
}
|
||||
};
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
DEFINE_TEST("round trip")
|
||||
{
|
||||
using namespace code::json::marshaling;
|
||||
|
||||
person p1{ { "Jane", "Doe" }, 37 };
|
||||
|
||||
auto p2 = deserialize< person >(serialize(p1));
|
||||
|
||||
TEST_EQUAL(p1.name.first, p2.name.first);
|
||||
TEST_EQUAL(p1.name.last, p2.name.last);
|
||||
TEST_EQUAL(p1.age, p2.age);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
91
code/json/serialize.hxx
Normal file
91
code/json/serialize.hxx
Normal file
@ -0,0 +1,91 @@
|
||||
#ifndef code__json__serialize_hxx_
|
||||
#define code__json__serialize_hxx_
|
||||
|
||||
#include <code/json/marshaling.hxx>
|
||||
|
||||
#include <code/json/read.hxx>
|
||||
#include <code/json/write.hxx>
|
||||
|
||||
#include <istream>
|
||||
|
||||
namespace code::json
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
serialize(std::ostream& o, T const& model)
|
||||
{
|
||||
write(o, marshal(model));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
serialize(std::ostream&& o, T const& model)
|
||||
{
|
||||
serialize(o, model);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string
|
||||
serialize(T const& model)
|
||||
{
|
||||
std::ostringstream str;
|
||||
serialize(str, model);
|
||||
return str.str();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
deserialize(diagnostics& d,
|
||||
std::istream& i,
|
||||
marshaling_context_t* context = nullptr)
|
||||
{
|
||||
return unmarshal<T>(read(d, i), context);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
deserialize(std::istream& i, marshaling_context_t* context = nullptr)
|
||||
{
|
||||
return unmarshal<T>(read(i), context);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
deserialize(diagnostics& d,
|
||||
std::istream&& i,
|
||||
marshaling_context_t* context = nullptr)
|
||||
{
|
||||
return deserialize<T>(d, i, context);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
deserialize(std::istream&& i, marshaling_context_t* context = nullptr)
|
||||
{
|
||||
return deserialize<T>(i, context);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
deserialize(diagnostics& d,
|
||||
std::string const& str,
|
||||
marshaling_context_t* context = nullptr)
|
||||
{
|
||||
// TODO use std::string overload of read
|
||||
return deserialize<T>(
|
||||
d, std::istringstream{ str, std::ios::in | std::ios::binary }, context);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
deserialize(std::string const& str, marshaling_context_t* context = nullptr)
|
||||
{
|
||||
// TODO use std::string overload of read
|
||||
return deserialize<T>(
|
||||
std::istringstream{ str, std::ios::in | std::ios::binary }, context);
|
||||
}
|
||||
|
||||
} // namespace code::json
|
||||
|
||||
#endif
|
55
code/json/serialize.test.cxx
Normal file
55
code/json/serialize.test.cxx
Normal file
@ -0,0 +1,55 @@
|
||||
#include <code/json/marshaling.hxx>
|
||||
#include <code/json/serialize.hxx>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#define DEFINE_TEST(x) std::cout << x << '\n';
|
||||
#define TEST_TRUE(x) if (!(x)) return __LINE__;
|
||||
#define TEST_FALSE(x) if ((x)) return __LINE__;
|
||||
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
|
||||
|
||||
using code::json::mapping_t;
|
||||
using code::json::member_t;
|
||||
using code::json::serialize;
|
||||
using code::json::deserialize;
|
||||
|
||||
struct person_name {
|
||||
std::string first;
|
||||
std::string last;
|
||||
|
||||
using json = mapping_t<
|
||||
person_name,
|
||||
member_t<"first", &person_name::first>,
|
||||
member_t<"last", &person_name::last>
|
||||
>;
|
||||
|
||||
};
|
||||
|
||||
struct person {
|
||||
person_name name;
|
||||
int age;
|
||||
|
||||
using json = mapping_t<
|
||||
person,
|
||||
member_t<"name", &person::name>,
|
||||
member_t<"age", &person::age>
|
||||
>;
|
||||
|
||||
};
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
DEFINE_TEST("round trip")
|
||||
{
|
||||
person p1{{"Jane", "Doe"}, 37};
|
||||
|
||||
auto p2 = deserialize<person>(serialize(p1));
|
||||
|
||||
TEST_EQUAL(p1.name.first, p2.name.first);
|
||||
TEST_EQUAL(p1.name.last, p2.name.last);
|
||||
TEST_EQUAL(p1.age, p2.age);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user