Reimplement marshaling

Reimplement marshaling using compile-time templates.

Closes #1
This commit is contained in:
G.H.O.S.T 2024-12-27 00:23:22 +01:00
parent 53255d1e23
commit fd27637d3d
Signed by: G.H.O.S.T
GPG Key ID: 3BD93EABD1407B82
12 changed files with 894 additions and 1092 deletions

699
code/json/marshaling.hxx Normal file
View 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

View File

@ -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,14 +14,33 @@
#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;
std::optional<int> model;
auto j = traits_type::marshal(model, nullptr);
@ -30,9 +49,9 @@ 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 };
std::optional<int> model{0};
auto j = traits_type::marshal(model, nullptr);
@ -42,9 +61,9 @@ 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" };
std::string model{"hello, world"};
auto j = traits_type::marshal(model, nullptr);
@ -54,9 +73,9 @@ 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;
std::vector<int> model1;
auto j = traits_type::marshal(model1, nullptr);
@ -69,9 +88,9 @@ 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 };
std::vector<int> model1{ 1, 2, 3, 4 };
auto j = traits_type::marshal(model1, nullptr);
@ -84,9 +103,9 @@ 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;
std::list<int> model1;
auto j = traits_type::marshal(model1, nullptr);
@ -99,9 +118,9 @@ 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 };
std::list<int> model1{ 1, 2, 3, 4 };
auto j = traits_type::marshal(model1, nullptr);
@ -114,9 +133,9 @@ 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;
std::deque<int> model1;
auto j = traits_type::marshal(model1, nullptr);
@ -129,9 +148,9 @@ 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 };
std::deque<int> model1{ 1, 2, 3, 4 };
auto j = traits_type::marshal(model1, nullptr);
@ -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;
}

View File

@ -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

View File

@ -1,8 +0,0 @@
#include <code/json/marshaling/marshaling-context.hxx>
namespace code::json::marshaling {
marshaling_context::~marshaling_context()
{}
} // namespace code::json::marshaling

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View 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

View 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;
}