From e5f6620a7b3c8839065588642303133b49084259 Mon Sep 17 00:00:00 2001 From: "Damien Buhl (alias daminetreg)" Date: Thu, 28 Oct 2021 13:56:36 +0200 Subject: [PATCH 1/3] :wink: to_json enabled for compile time static_string named field. --- .nxxm/deps | 4 --- .tipi/deps | 6 ++++ pre/cx/key_value_pair.hpp | 24 ++++++++++++++ pre/json/detail/jsonizer.hpp | 23 +++++++++++++ pre/json/detail/sfinae_enabler.hpp | 8 ++++- test/dejsonize_test.cpp | 52 ++++++++++++++++++++++++++++++ 6 files changed, 112 insertions(+), 5 deletions(-) delete mode 100644 .nxxm/deps create mode 100644 .tipi/deps create mode 100644 pre/cx/key_value_pair.hpp diff --git a/.nxxm/deps b/.nxxm/deps deleted file mode 100644 index 3f82acdd..00000000 --- a/.nxxm/deps +++ /dev/null @@ -1,4 +0,0 @@ -{ - "nlohmann/json" : { "@" : "v3.1.2", "x" : ["benchmarks"] } - , "platform" : [ "Boost::+boost" ] -} diff --git a/.tipi/deps b/.tipi/deps new file mode 100644 index 00000000..8c109b3e --- /dev/null +++ b/.tipi/deps @@ -0,0 +1,6 @@ +{ + "nlohmann/json" : { "@" : "v3.1.2", "x" : ["benchmarks"] } + , "boostorg/pfr" : {} + , "tipi-build/constexpr_all_the_things" : { "@" : "feature/json-substrings-as-types" } + , "platform" : [ "Boost::+boost" ] +} diff --git a/pre/cx/key_value_pair.hpp b/pre/cx/key_value_pair.hpp new file mode 100644 index 00000000..688298cd --- /dev/null +++ b/pre/cx/key_value_pair.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace pre::cx { + using ::cx::static_string; + template + struct tstring { + static constexpr cx::static_string value = str; + }; + + template + struct key_value_pair { + using key = tstring; + T value; + }; + + template + auto get_key(const T&) { + return T::key::value.c_str(); + }; + +} \ No newline at end of file diff --git a/pre/json/detail/jsonizer.hpp b/pre/json/detail/jsonizer.hpp index c11ffe1a..3b41dabe 100644 --- a/pre/json/detail/jsonizer.hpp +++ b/pre/json/detail/jsonizer.hpp @@ -15,6 +15,12 @@ #include
 #include 
 
+#include 
+
+#include 
+#include 
+#include 
+
 //TODO: What about tuples ?
 //TODO: What about normal union?
 
@@ -119,6 +125,23 @@ namespace pre { namespace json { namespace detail {
       }
     }
 
+    //! Named fields
+    template
+    void operator()(const pre::cx::key_value_pair& value) const {
+        this->operator()(get_key(value), value.value);
+    }
+
+    //! Non adapted structs with key_value_pair
+    template* = nullptr>
+    void operator()(const T& value) const {
+      auto t = boost::pfr::structure_to_tuple(value);
+      boost::hana::for_each(t, 
+      [this](pre::cx::key_value_pair x) {
+        this->operator()(x);
+      });
+    }
+
     
     private:
       nlohmann::json& _json_object;
diff --git a/pre/json/detail/sfinae_enabler.hpp b/pre/json/detail/sfinae_enabler.hpp
index 583c5d38..e487092e 100644
--- a/pre/json/detail/sfinae_enabler.hpp
+++ b/pre/json/detail/sfinae_enabler.hpp
@@ -50,6 +50,13 @@ namespace pre { namespace json { namespace detail {
       >::value
     ,T>::type;
 
+    template
+    using enable_if_is_struct_non_adapted_t = typename std::enable_if< 
+      ! std::is_same<
+        typename boost::fusion::traits::tag_of::type, 
+        boost::fusion::struct_tag
+      >::value && std::is_aggregate_v
+    ,T>::type;
 
     template
     using enable_if_is_container_t = typename std::enable_if<
@@ -61,7 +68,6 @@ namespace pre { namespace json { namespace detail {
       traits::is_associative_container::value 
     ,T>::type;
 
-
 }}}
 
 #endif
diff --git a/test/dejsonize_test.cpp b/test/dejsonize_test.cpp
index 084277ca..c7cd414d 100644
--- a/test/dejsonize_test.cpp
+++ b/test/dejsonize_test.cpp
@@ -616,3 +616,55 @@ BOOST_AUTO_TEST_CASE (std_variant_complex) {
 
 
 }
+
+#define json_key(type, name) ::pre::cx::key_value_pair<"" # name, type> name
+
+namespace datamodel {
+  struct BuildRunnerRequestDto {
+    json_key(std::string, ssh_public_key);
+    json_key(std::string, environment_name);
+    json_key(std::string, environment_zip_hash);
+    json_key(std::size_t, job_size);
+    json_key(std::string, environment_zip);
+    json_key(cashier, user);
+  };
+}
+
+BOOST_AUTO_TEST_CASE (compile_time_named_fields_no_adapt_struct) {
+
+  {
+    using datamodel::BuildRunnerRequestDto;
+    BuildRunnerRequestDto val{.ssh_public_key = { "that's one key"} };
+
+    auto val_json = pre::json::to_json(val);
+    std::cout << val_json.dump(2) << std::endl;
+
+    //auto val_deserialized = pre::json::from_json(val_json); 
+
+    //auto val_reserialized = pre::json::to_json(val_deserialized);
+    //std::cout << val_reserialized.dump(2) << std::endl;
+
+    //BOOST_REQUIRE(val == val_deserialized);
+  }
+
+  {
+    using datamodel::BuildRunnerRequestDto;
+    std::vector vals{
+      {.ssh_public_key = { "that's one key"}, .job_size = {11}},
+      {.ssh_public_key = { "that's second key"}, .job_size = {43}},
+      {.ssh_public_key = { "that's third key"}, .job_size = {13}}
+    };
+
+    auto val_json = pre::json::to_json(vals);
+    std::cout << val_json.dump(2) << std::endl;
+
+    //auto val_deserialized = pre::json::from_json(val_json); 
+
+    //auto val_reserialized = pre::json::to_json(val_deserialized);
+    //std::cout << val_reserialized.dump(2) << std::endl;
+
+    //BOOST_REQUIRE(val == val_deserialized);
+  }
+
+
+}
\ No newline at end of file

From c3c6580b86409b635da81a2cb2e3b3d2e227f630 Mon Sep 17 00:00:00 2001
From: "Damien Buhl (alias daminetreg)" 
Date: Thu, 28 Oct 2021 15:34:24 +0200
Subject: [PATCH 2/3] :wink: from_json for any simple aggregate types and
 compile-time-named key_value_pairs.

---
 pre/cx/key_value_pair.hpp      | 33 +++++++++++++++++++++++++++++
 pre/json/detail/dejsonizer.hpp | 26 +++++++++++++++++++++++
 pre/json/detail/jsonizer.hpp   |  2 +-
 test/dejsonize_test.cpp        | 38 +++++++++++++++++-----------------
 4 files changed, 79 insertions(+), 20 deletions(-)

diff --git a/pre/cx/key_value_pair.hpp b/pre/cx/key_value_pair.hpp
index 688298cd..42994f54 100644
--- a/pre/cx/key_value_pair.hpp
+++ b/pre/cx/key_value_pair.hpp
@@ -3,6 +3,8 @@
 #include 
 #include 
 
+#define pre_json_key(type, name) ::pre::cx::key_value_pair<"" # name, type> name
+
 namespace pre::cx {
   using ::cx::static_string;
   template 
@@ -14,11 +16,42 @@ namespace pre::cx {
   struct key_value_pair { 
     using key = tstring;
     T value;
+    friend bool operator<(const key_value_pair& x, const key_value_pair& y) {
+      return x.value < y.value; 
+    }
+    template friend bool operator==(const key_value_pair& lhs, const U& rhs){return lhs.value == rhs.value;}
+    template friend bool operator< (const key_value_pair& lhs, const U& rhs){return lhs.value < rhs.value;}
+    template friend bool operator!=(const key_value_pair& lhs, const U& rhs){return !operator==(lhs,rhs);}
+    template friend bool operator> (const key_value_pair& lhs, const U& rhs){return  operator< (rhs,lhs);}
+    template friend bool operator<=(const key_value_pair& lhs, const U& rhs){return !operator> (lhs,rhs);}
+    template friend bool operator>=(const key_value_pair& lhs, const U& rhs){return !operator< (lhs,rhs);}
+  };
+}
+
+namespace std {
+  template 
+  class hash> {
+  public:
+    size_t operator()(const pre::cx::key_value_pair &k) const
+    {
+      return std::hash{}(key_.c_str());
+    }
   };
+}
 
+namespace pre::cx {
   template
   auto get_key(const T&) {
     return T::key::value.c_str();
   };
 
+  /*template 
+  inline bool operator==(key_value_pair const& a, key_value_pair const& b) {
+    return a.value == b.value;
+  }
+  template 
+  inline bool operator<(key_value_pair const& a, key_value_pair const& b) {
+    return a.value < b.value;
+  }*/
+
 }
\ No newline at end of file
diff --git a/pre/json/detail/dejsonizer.hpp b/pre/json/detail/dejsonizer.hpp
index 139ac1df..03fd2105 100644
--- a/pre/json/detail/dejsonizer.hpp
+++ b/pre/json/detail/dejsonizer.hpp
@@ -17,6 +17,12 @@
 
 #include 
 
+#include 
+
+#include 
+#include 
+#include 
+
 namespace pre { namespace json { namespace detail {
 
   struct dejsonizer {
@@ -159,6 +165,26 @@ namespace pre { namespace json { namespace detail {
         throw std::runtime_error("Expected " + _json_object.dump() + " to be a json object.");
       }
     }
+
+
+    //! Named fields
+    template
+    void operator()(pre::cx::key_value_pair& value) const {
+        this->operator()(get_key(value), value.value);
+    }
+
+    //! Non adapted structs with key_value_pair
+    template* = nullptr>
+    void operator()(T& value) const {
+
+                
+      auto t = boost::pfr::structure_tie(value);
+      boost::hana::for_each(t, 
+      [this](pre::cx::key_value_pair& x) {
+        this->operator()(x);
+      });
+    }
       
     private:
       const nlohmann::json& _json_object; // XXX: Invert to be the same as jsonizer
diff --git a/pre/json/detail/jsonizer.hpp b/pre/json/detail/jsonizer.hpp
index 3b41dabe..a03da6e1 100644
--- a/pre/json/detail/jsonizer.hpp
+++ b/pre/json/detail/jsonizer.hpp
@@ -135,7 +135,7 @@ namespace pre { namespace json { namespace detail {
     template* = nullptr>
     void operator()(const T& value) const {
-      auto t = boost::pfr::structure_to_tuple(value);
+      auto t = boost::pfr::structure_tie(value);
       boost::hana::for_each(t, 
       [this](pre::cx::key_value_pair x) {
         this->operator()(x);
diff --git a/test/dejsonize_test.cpp b/test/dejsonize_test.cpp
index c7cd414d..2f793bec 100644
--- a/test/dejsonize_test.cpp
+++ b/test/dejsonize_test.cpp
@@ -7,7 +7,7 @@
 #include 
 #include 
 
-#include 
+#include 
 #include 
 #include 
 
@@ -22,6 +22,7 @@
 namespace datamodel {
 
   using boost::fusion::operator==;
+  using boost::fusion::operator<;
   using boost::fusion::operator!=;
 
   typedef size_t years;
@@ -617,34 +618,33 @@ BOOST_AUTO_TEST_CASE (std_variant_complex) {
 
 }
 
-#define json_key(type, name) ::pre::cx::key_value_pair<"" # name, type> name
-
 namespace datamodel {
   struct BuildRunnerRequestDto {
-    json_key(std::string, ssh_public_key);
-    json_key(std::string, environment_name);
-    json_key(std::string, environment_zip_hash);
-    json_key(std::size_t, job_size);
-    json_key(std::string, environment_zip);
-    json_key(cashier, user);
+    pre_json_key(std::string, ssh_public_key);
+    pre_json_key(std::string, environment_name);
+    pre_json_key(std::string, environment_zip_hash);
+    pre_json_key(std::size_t, job_size);
+    pre_json_key(std::string, environment_zip);
+    pre_json_key(cashier, user);
   };
+  BOOST_PFR_FUNCTIONS_FOR(BuildRunnerRequestDto);
 }
 
 BOOST_AUTO_TEST_CASE (compile_time_named_fields_no_adapt_struct) {
 
   {
     using datamodel::BuildRunnerRequestDto;
-    BuildRunnerRequestDto val{.ssh_public_key = { "that's one key"} };
+    BuildRunnerRequestDto val{ .ssh_public_key = { "that's one key"} };
 
     auto val_json = pre::json::to_json(val);
     std::cout << val_json.dump(2) << std::endl;
 
-    //auto val_deserialized = pre::json::from_json(val_json); 
+    auto val_deserialized = pre::json::from_json(val_json); 
 
-    //auto val_reserialized = pre::json::to_json(val_deserialized);
-    //std::cout << val_reserialized.dump(2) << std::endl;
+    auto val_reserialized = pre::json::to_json(val_deserialized);
+    std::cout << val_reserialized.dump(2) << std::endl;
 
-    //BOOST_REQUIRE(val == val_deserialized);
+    BOOST_REQUIRE(val == val_deserialized);
   }
 
   {
@@ -652,18 +652,18 @@ BOOST_AUTO_TEST_CASE (compile_time_named_fields_no_adapt_struct) {
     std::vector vals{
       {.ssh_public_key = { "that's one key"}, .job_size = {11}},
       {.ssh_public_key = { "that's second key"}, .job_size = {43}},
-      {.ssh_public_key = { "that's third key"}, .job_size = {13}}
+      {.ssh_public_key = { "that's third key"}, .job_size = {13}, .user{{ .section="banananas", .checkout_number=23 }} }
     };
 
     auto val_json = pre::json::to_json(vals);
     std::cout << val_json.dump(2) << std::endl;
 
-    //auto val_deserialized = pre::json::from_json(val_json); 
+    auto val_deserialized = pre::json::from_json(val_json); 
 
-    //auto val_reserialized = pre::json::to_json(val_deserialized);
-    //std::cout << val_reserialized.dump(2) << std::endl;
+    auto val_reserialized = pre::json::to_json(val_deserialized);
+    std::cout << val_reserialized.dump(2) << std::endl;
 
-    //BOOST_REQUIRE(val == val_deserialized);
+    BOOST_REQUIRE(vals == val_deserialized);
   }
 
 

From 7e5a89463084cfaaa2f30327dbba286de100d915 Mon Sep 17 00:00:00 2001
From: "Damien Buhl (alias daminetreg)" 
Date: Thu, 28 Oct 2021 16:45:04 +0200
Subject: [PATCH 3/3] :wink: wokrking serialization of tuples to json.

---
 pre/json/detail/dejsonizer.hpp     | 11 +++++-
 pre/json/detail/jsonizer.hpp       | 11 +++++-
 pre/json/detail/sfinae_enabler.hpp | 23 +++++++++++
 test/dejsonize_test.cpp            | 61 ++++++++++++++++++++++++++++++
 4 files changed, 104 insertions(+), 2 deletions(-)

diff --git a/pre/json/detail/dejsonizer.hpp b/pre/json/detail/dejsonizer.hpp
index 03fd2105..264f6829 100644
--- a/pre/json/detail/dejsonizer.hpp
+++ b/pre/json/detail/dejsonizer.hpp
@@ -181,7 +181,16 @@ namespace pre { namespace json { namespace detail {
                 
       auto t = boost::pfr::structure_tie(value);
       boost::hana::for_each(t, 
-      [this](pre::cx::key_value_pair& x) {
+      [this](auto& x) {
+        this->operator()(x);
+      });
+    }
+
+    //! tuples
+    template
+    void operator()(T& value) const {
+      boost::hana::for_each(value, 
+      [this](auto& x) {
         this->operator()(x);
       });
     }
diff --git a/pre/json/detail/jsonizer.hpp b/pre/json/detail/jsonizer.hpp
index a03da6e1..0cad4287 100644
--- a/pre/json/detail/jsonizer.hpp
+++ b/pre/json/detail/jsonizer.hpp
@@ -137,7 +137,16 @@ namespace pre { namespace json { namespace detail {
     void operator()(const T& value) const {
       auto t = boost::pfr::structure_tie(value);
       boost::hana::for_each(t, 
-      [this](pre::cx::key_value_pair x) {
+      [this](const auto& x) {
+        this->operator()(x);
+      });
+    }
+
+    //! tuples
+    template
+    void operator()(const T& value) const {
+      boost::hana::for_each(value, 
+      [this](const auto& x) {
         this->operator()(x);
       });
     }
diff --git a/pre/json/detail/sfinae_enabler.hpp b/pre/json/detail/sfinae_enabler.hpp
index e487092e..8343d512 100644
--- a/pre/json/detail/sfinae_enabler.hpp
+++ b/pre/json/detail/sfinae_enabler.hpp
@@ -68,6 +68,29 @@ namespace pre { namespace json { namespace detail {
       traits::is_associative_container::value 
     ,T>::type;
 
+    namespace requirements {
+      template
+      concept has_tuple_element =
+        requires(T t) {
+          typename std::tuple_element_t>;
+          { get(t) } -> std::convertible_to&>;
+        };
+
+      template
+      concept tuple_like = !std::is_reference_v 
+        && requires(T t) { 
+          typename std::tuple_size::type; 
+          requires std::derived_from<
+            std::tuple_size, 
+            std::integral_constant>
+          >;
+        } && [](std::index_sequence) { 
+          return (has_tuple_element && ...); 
+        }(std::make_index_sequence>());
+
+      template
+      concept not_tuple_like = !tuple_like;
+    }
 }}}
 
 #endif
diff --git a/test/dejsonize_test.cpp b/test/dejsonize_test.cpp
index 2f793bec..52e2d1e1 100644
--- a/test/dejsonize_test.cpp
+++ b/test/dejsonize_test.cpp
@@ -666,5 +666,66 @@ BOOST_AUTO_TEST_CASE (compile_time_named_fields_no_adapt_struct) {
     BOOST_REQUIRE(vals == val_deserialized);
   }
 
+  {
+    using pre::cx::key_value_pair;
+    using namespace datamodel;
+    using namespace std::literals;
+
+    auto val = std::make_tuple(
+      key_value_pair<"ssh_public_key"         , std::string>  {"key"s},
+      key_value_pair<"environment_name"       , std::string>  {"env"s},
+      key_value_pair<"environment_zip_hash"   , std::string>  {"zip_hash"s},
+      key_value_pair<"job_size"               , std::size_t>  {434343},
+      key_value_pair<"environment_zip"        , std::string>  {"zip"s},
+      key_value_pair<"user"                   , cashier>      {{ .section="wine & spirits", .checkout_number=27}} 
+    );
+
+    auto val_json = pre::json::to_json(val);
+    std::cout << val_json.dump(2) << std::endl;
+
+    auto val_deserialized = pre::json::from_json(val_json); 
+
+    auto val_reserialized = pre::json::to_json(val_deserialized);
+    std::cout << val_reserialized.dump(2) << std::endl;
+
+    BOOST_REQUIRE(val == val_deserialized);
+  }
+
+
+    {
+    using pre::cx::key_value_pair;
+    using namespace datamodel;
+    using namespace std::literals;
+
+    auto val = std::make_tuple(
+      key_value_pair<"ssh_public_key"         , std::string>  {"key"s},
+      key_value_pair<"environment_name"       , std::string>  {"env"s},
+      key_value_pair<"environment_zip_hash"   , std::string>  {"zip_hash"s},
+      key_value_pair<"job_size"               , std::size_t>  {434343},
+      key_value_pair<"environment_zip"        , std::string>  {"zip"s},
+      key_value_pair<"user"                   , cashier>      {{ .section="wine & spirits", .checkout_number=27}} 
+    );
+
+    std::vector vals { val };
+    vals.push_back(std::make_tuple(
+      key_value_pair<"ssh_public_key"         , std::string>  {"yoooo"s},
+      key_value_pair<"environment_name"       , std::string>  {"woui"s},
+      key_value_pair<"environment_zip_hash"   , std::string>  {"noooooo"s},
+      key_value_pair<"job_size"               , std::size_t>  {540210},
+      key_value_pair<"environment_zip"        , std::string>  {"timeeeee"s},
+      key_value_pair<"user"                   , cashier>      {{ .section="Gemüse", .checkout_number=3486}} 
+    ));
+
+    auto val_json = pre::json::to_json(vals);
+    std::cout << val_json.dump(2) << std::endl;
+
+    auto val_deserialized = pre::json::from_json(val_json); 
+
+    auto val_reserialized = pre::json::to_json(val_deserialized);
+    std::cout << val_reserialized.dump(2) << std::endl;
+
+    BOOST_REQUIRE(vals == val_deserialized);
+  }
+
 
 }
\ No newline at end of file