From fd27637d3df0f900ae5d592003ba3fdd0f0eed2e Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 27 Dec 2024 00:23:22 +0100 Subject: [PATCH] Reimplement marshaling Reimplement marshaling using compile-time templates. Closes #1 --- code/json/marshaling.hxx | 699 ++++++++++++++++++ ...ng-traits.test.cxx => marshaling.test.cxx} | 69 +- code/json/marshaling/mapping.hxx | 245 ------ code/json/marshaling/marshaling-context.cxx | 8 - code/json/marshaling/marshaling-context.hxx | 13 - code/json/marshaling/marshaling-traits.hxx | 626 ---------------- code/json/marshaling/marshaling.hxx | 29 - code/json/marshaling/serialize.hxx | 91 --- code/json/marshaling/serialize.test.cxx | 60 -- code/json/serialize.hxx | 91 +++ code/json/serialize.test.cxx | 55 ++ code/json/{marshaling => }/traits.hxx | 0 12 files changed, 894 insertions(+), 1092 deletions(-) create mode 100644 code/json/marshaling.hxx rename code/json/{marshaling/marshaling-traits.test.cxx => marshaling.test.cxx} (57%) delete mode 100644 code/json/marshaling/mapping.hxx delete mode 100644 code/json/marshaling/marshaling-context.cxx delete mode 100644 code/json/marshaling/marshaling-context.hxx delete mode 100644 code/json/marshaling/marshaling-traits.hxx delete mode 100644 code/json/marshaling/marshaling.hxx delete mode 100644 code/json/marshaling/serialize.hxx delete mode 100644 code/json/marshaling/serialize.test.cxx create mode 100644 code/json/serialize.hxx create mode 100644 code/json/serialize.test.cxx rename code/json/{marshaling => }/traits.hxx (100%) diff --git a/code/json/marshaling.hxx b/code/json/marshaling.hxx new file mode 100644 index 0000000..3f921f6 --- /dev/null +++ b/code/json/marshaling.hxx @@ -0,0 +1,699 @@ +#ifndef code__json__marshaling_hxx_ +#define code__json__marshaling_hxx_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace code::json +{ + + class marshaling_context_t + { + protected: + virtual ~marshaling_context_t() = default; + + }; + + template + struct marshaling_traits; + + template + 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 + struct member_traits; + + template + struct member_traits + { + using class_type = T; + using member_type = M; + + }; + + template + struct member_t + { + using T = member_traits::class_type; + using M = member_traits::member_type; + + static + void + marshal(variant& v, T const& instance, marshaling_context_t* context) + { + v.set(Name, marshaling_traits::marshal(instance.*Member, context)); + } + + static + void + unmarshal(T& instance, variant const& v, marshaling_context_t* context) + { + instance.*Member = marshaling_traits::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::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::unmarshal(*ptr_v, context); + } + + }; + + }; + + template + struct mapping_t + { + static + variant + marshal(T const& instance, marshaling_context_t* context) + { + variant v{std::map{}}; + ((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 + 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 + struct marshaling_traits> + { + static + variant + marshal(optional const& model, marshaling_context_t* context) + { + if (model) { + return marshaling_traits::marshal(*model, context); + } + + return {}; + } + + static optional + unmarshal(variant const& value, marshaling_context_t* context) + { + if (value.is_undefined()) { + return {}; + } + + return marshaling_traits::unmarshal(value, context); + } + }; + + template + struct marshaling_traits> + { + static + variant + marshal(std::variant const& model, marshaling_context_t* context) + { + variant result; + + auto visitor = [&result, context](const auto& obj) + { + using type = std::decay_t; + result = marshaling_traits::marshal(obj, context); + result.set("$type", std::decay_t::type_identifier); + }; + + std::visit(visitor, model); + + return result; + } + + template + struct unmarshaler + { + static + std::variant + 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::unmarshal(value, context); + + if constexpr (sizeof...(Next)> 0) + return unmarshaler::unmarshal(value, context); + + throw std::invalid_argument{"couldn't unmarshal value"}; + } + }; + + static + std::variant + unmarshal(variant const& value, marshaling_context_t* context) + { + return unmarshaler::unmarshal(value, context); + } + }; + + template + struct marshaling_traits> + { + static + variant + marshal(std::vector const& model, marshaling_context_t* context) + { + std::vector a; + + for (auto const& j : model) + a.emplace_back(marshaling_traits::marshal(j, context)); + + return a; + } + + static + std::vector + unmarshal(variant const& value, marshaling_context_t* context) + { + if (!value.is_array()) + throw std::runtime_error{"not an array"}; + + std::vector model; + + for (auto const& j : value) + model.emplace_back(marshaling_traits::unmarshal(j, context)); + + return model; + } + }; + + template + struct marshaling_traits> + { + static + variant + marshal(std::list const& model, marshaling_context_t* context) + { + std::vector a; + + for (auto const& j : model) + a.emplace_back(marshaling_traits::marshal(j, context)); + + return a; + } + + static + std::list + unmarshal(variant const& value, marshaling_context_t* context) + { + if (!value.is_array()) + throw std::runtime_error{"not an array"}; + + std::list model; + + for (auto const& j : value) + model.emplace_back(marshaling_traits::unmarshal(j, context)); + + return model; + } + }; + + template + struct marshaling_traits> + { + static + variant + marshal(std::deque const& model, marshaling_context_t* context) + { + std::vector a; + + for (auto const& j : model) + a.emplace_back(marshaling_traits::marshal(j, context)); + + return a; + } + + static + std::deque + unmarshal(variant const& value, marshaling_context_t* context) + { + if (!value.is_array()) + throw std::runtime_error{"not an array"}; + + std::deque model; + + for (auto const& j : value) + model.emplace_back(marshaling_traits::unmarshal(j, context)); + + return model; + } + }; + + template + struct marshaling_traits> + { + static + variant + marshal(std::set const& model, marshaling_context_t* context) + { + std::vector a; + + for (auto const& j : model) + a.emplace_back(marshaling_traits::marshal(j, context)); + + return a; + } + + static + std::set + unmarshal(variant const& value, marshaling_context_t* context) + { + if (!value.is_array()) + throw std::runtime_error{"not an array"}; + + std::set model; + + for (auto const& j : value) + model.emplace(marshaling_traits::unmarshal(j, context)); + + return model; + } + }; + + template<> + struct marshaling_traits + { + 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 { + 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(); + } + }; + + template<> + struct marshaling_traits + { + 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(); + } + + }; + + template<> + struct marshaling_traits { + 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(); + } + }; + + template<> + struct marshaling_traits { + 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(); + } + }; + + template<> + struct marshaling_traits { + 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(); + } + }; + + template<> + struct marshaling_traits { + 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(); + } + }; + + template<> + struct marshaling_traits { + 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 long int + + template<> + struct marshaling_traits { + 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(); + } + }; + + template<> + struct marshaling_traits { + 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(); + } + }; + + template<> + struct marshaling_traits { + 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(); + } + }; + + template<> + struct marshaling_traits { + 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(); + } + }; + + template<> + struct marshaling_traits + { + 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 + { + 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 + variant + marshal(T const& model, marshaling_context_t* context = nullptr) + { + return marshaling_traits::marshal(model, context); + } + + template + T + unmarshal(variant const& v, marshaling_context_t* context = nullptr) + { + return marshaling_traits::unmarshal(v, context); + } + +} // namespace code::json + +#endif diff --git a/code/json/marshaling/marshaling-traits.test.cxx b/code/json/marshaling.test.cxx similarity index 57% rename from code/json/marshaling/marshaling-traits.test.cxx rename to code/json/marshaling.test.cxx index 3621b59..50ac03b 100644 --- a/code/json/marshaling/marshaling-traits.test.cxx +++ b/code/json/marshaling.test.cxx @@ -1,6 +1,6 @@ +#include #include - -#include +#include #include #include @@ -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{}") { - using traits_type = code::json::marshaling::marshaling_traits< code::json::optional< int > >; + using traits_type = marshaling_traits>; - std::optional< int > model; + std::optional model; auto j = traits_type::marshal(model, nullptr); @@ -30,9 +49,9 @@ main() DEFINE_TEST("optional{0}") { - using traits_type = code::json::marshaling::marshaling_traits< code::json::optional< int > >; + using traits_type = marshaling_traits>; - std::optional< int > model{ 0 }; + std::optional 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 model{ "hello, world" }; + std::string model{"hello, world"}; auto j = traits_type::marshal(model, nullptr); @@ -54,9 +73,9 @@ main() DEFINE_TEST("vector{}") { - using traits_type = code::json::marshaling::marshaling_traits< std::vector< int > >; + using traits_type = marshaling_traits>; - std::vector< int > model1; + std::vector model1; auto j = traits_type::marshal(model1, nullptr); @@ -69,9 +88,9 @@ main() DEFINE_TEST("vector{...}") { - using traits_type = code::json::marshaling::marshaling_traits< std::vector< int > >; + using traits_type = marshaling_traits>; - std::vector< int > model1{ 1, 2, 3, 4 }; + std::vector model1{ 1, 2, 3, 4 }; auto j = traits_type::marshal(model1, nullptr); @@ -84,9 +103,9 @@ main() DEFINE_TEST("list{}") { - using traits_type = code::json::marshaling::marshaling_traits< std::list< int > >; + using traits_type = marshaling_traits>; - std::list< int > model1; + std::list model1; auto j = traits_type::marshal(model1, nullptr); @@ -99,9 +118,9 @@ main() DEFINE_TEST("list{...}") { - using traits_type = code::json::marshaling::marshaling_traits< std::list< int > >; + using traits_type = marshaling_traits>; - std::list< int > model1{ 1, 2, 3, 4 }; + std::list model1{ 1, 2, 3, 4 }; auto j = traits_type::marshal(model1, nullptr); @@ -114,9 +133,9 @@ main() DEFINE_TEST("deque{}") { - using traits_type = code::json::marshaling::marshaling_traits< std::deque< int > >; + using traits_type = marshaling_traits>; - std::deque< int > model1; + std::deque model1; auto j = traits_type::marshal(model1, nullptr); @@ -129,9 +148,9 @@ main() DEFINE_TEST("deque{...}") { - using traits_type = code::json::marshaling::marshaling_traits< std::deque< int > >; + using traits_type = marshaling_traits>; - std::deque< int > model1{ 1, 2, 3, 4 }; + std::deque 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(name1); + auto name2 = unmarshal(v); + + TEST_EQUAL(name1.first, name2.first); + TEST_EQUAL(name1.last, name2.last); + } + return 0; } diff --git a/code/json/marshaling/mapping.hxx b/code/json/marshaling/mapping.hxx deleted file mode 100644 index ffdaa2d..0000000 --- a/code/json/marshaling/mapping.hxx +++ /dev/null @@ -1,245 +0,0 @@ -#ifndef json__marshaling__mapping_hxx_ -#define json__marshaling__mapping_hxx_ - -#include -#include -#include - -#include -#include - -#include -#include - -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 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 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, - [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, - [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()))>>, - [access](T const& instance, marshaling_context* context) -> json::variant - { - using M = std::decay_t; - return marshaling_traits< M >::marshal(access(instance), context); - }, - [access](T& instance, json::variant const& v, marshaling_context* context) - { - using M = std::decay_t; - 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()))>>, - [access](T const& instance, marshaling_context* context) -> json::variant - { - using M = std::decay_t; - return marshaling_traits< M >::marshal(access(instance), context); - }, - [access](T& instance, json::variant const& v, marshaling_context* context) - { - using M = std::decay_t; - 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 -mapping -map(std::initializer_list> init) -{ - typename mapping::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 diff --git a/code/json/marshaling/marshaling-context.cxx b/code/json/marshaling/marshaling-context.cxx deleted file mode 100644 index 5eb4aed..0000000 --- a/code/json/marshaling/marshaling-context.cxx +++ /dev/null @@ -1,8 +0,0 @@ -#include - -namespace code::json::marshaling { - -marshaling_context::~marshaling_context() -{} - -} // namespace code::json::marshaling diff --git a/code/json/marshaling/marshaling-context.hxx b/code/json/marshaling/marshaling-context.hxx deleted file mode 100644 index 5718ab7..0000000 --- a/code/json/marshaling/marshaling-context.hxx +++ /dev/null @@ -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 diff --git a/code/json/marshaling/marshaling-traits.hxx b/code/json/marshaling/marshaling-traits.hxx deleted file mode 100644 index 61c584e..0000000 --- a/code/json/marshaling/marshaling-traits.hxx +++ /dev/null @@ -1,626 +0,0 @@ -#ifndef code__json__marshaling__marshaling_traits_hxx_ -#define code__json__marshaling__marshaling_traits_hxx_ - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include // TODO: Remove. -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -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{}}; - - // TODO: Handle mappings with pointers. - for (auto const& mapping : model_type::json()) { - if (std::holds_alternative(mapping.key())) { - auto ptr = std::get(mapping.key()); - ptr.write(v, mapping.get(model, context)); - } - else { - v.set(std::get(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(mapping.key())) { - auto ptr = std::get(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(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; - result = marshaling_traits::marshal(obj, context); - result.set("$type", std::decay_t::type_identifier); - }; - - std::visit(visitor, model); - - return result; - } - - template - struct unmarshaler - { - static - std::variant - 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::unmarshal(value, context); - - throw std::invalid_argument{"couldn't unmarshal value"}; - } - }; - - static std::variant< T... > - unmarshal(variant const& value, marshaling_context* context) - { - return unmarshaler::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 -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 diff --git a/code/json/marshaling/marshaling.hxx b/code/json/marshaling/marshaling.hxx deleted file mode 100644 index ec8fb30..0000000 --- a/code/json/marshaling/marshaling.hxx +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef code__json__marshaling__marshaling_hxx_ -#define code__json__marshaling__marshaling_hxx_ - -#include - -#include -#include - -#include - -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 diff --git a/code/json/marshaling/serialize.hxx b/code/json/marshaling/serialize.hxx deleted file mode 100644 index 6237a47..0000000 --- a/code/json/marshaling/serialize.hxx +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef code__json__marshaling__serialize_hxx_ -#define code__json__marshaling__serialize_hxx_ - -#include -#include - -#include -#include - -#include - -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 diff --git a/code/json/marshaling/serialize.test.cxx b/code/json/marshaling/serialize.test.cxx deleted file mode 100644 index a8f669b..0000000 --- a/code/json/marshaling/serialize.test.cxx +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include - -#include - -#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; -} diff --git a/code/json/serialize.hxx b/code/json/serialize.hxx new file mode 100644 index 0000000..8ace6b6 --- /dev/null +++ b/code/json/serialize.hxx @@ -0,0 +1,91 @@ +#ifndef code__json__serialize_hxx_ +#define code__json__serialize_hxx_ + +#include + +#include +#include + +#include + +namespace code::json +{ + + template + void + serialize(std::ostream& o, T const& model) + { + write(o, marshal(model)); + } + + template + void + serialize(std::ostream&& o, T const& model) + { + serialize(o, model); + } + + template + std::string + serialize(T const& model) + { + std::ostringstream str; + serialize(str, model); + return str.str(); + } + + template + T + deserialize(diagnostics& d, + std::istream& i, + marshaling_context_t* context = nullptr) + { + return unmarshal(read(d, i), context); + } + + template + T + deserialize(std::istream& i, marshaling_context_t* context = nullptr) + { + return unmarshal(read(i), context); + } + + template + T + deserialize(diagnostics& d, + std::istream&& i, + marshaling_context_t* context = nullptr) + { + return deserialize(d, i, context); + } + + template + T + deserialize(std::istream&& i, marshaling_context_t* context = nullptr) + { + return deserialize(i, context); + } + + template + T + deserialize(diagnostics& d, + std::string const& str, + marshaling_context_t* context = nullptr) + { + // TODO use std::string overload of read + return deserialize( + d, std::istringstream{ str, std::ios::in | std::ios::binary }, context); + } + + template + T + deserialize(std::string const& str, marshaling_context_t* context = nullptr) + { + // TODO use std::string overload of read + return deserialize( + std::istringstream{ str, std::ios::in | std::ios::binary }, context); + } + +} // namespace code::json + +#endif diff --git a/code/json/serialize.test.cxx b/code/json/serialize.test.cxx new file mode 100644 index 0000000..c72b9ce --- /dev/null +++ b/code/json/serialize.test.cxx @@ -0,0 +1,55 @@ +#include +#include + +#include + +#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(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; +} diff --git a/code/json/marshaling/traits.hxx b/code/json/traits.hxx similarity index 100% rename from code/json/marshaling/traits.hxx rename to code/json/traits.hxx