Browse Source

Adding binders for pointer types and polymorphic types.

Sam Jaffe 7 years ago
parent
commit
7713bd7133

+ 8 - 1
json.xcodeproj/project.pbxproj

@@ -60,11 +60,14 @@
 		CD17473E1D4C1DFD000C344B /* json_binder_discard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = json_binder_discard.cpp; sourceTree = "<group>"; };
 		CD17473F1D4C1DFD000C344B /* json_binder_discard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = json_binder_discard.hpp; sourceTree = "<group>"; };
 		CD1747431D4C216B000C344B /* example.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = example.json; sourceTree = "<group>"; };
+		CD1AD696219E60DC00AB8959 /* json_pointer_binder.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json_pointer_binder.hpp; sourceTree = "<group>"; };
+		CD1AD697219E615700AB8959 /* json_polymorphic_binder.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json_polymorphic_binder.hpp; sourceTree = "<group>"; };
 		CD217D8F1CCAD587007C50C6 /* json_test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = json_test.cpp; sourceTree = "<group>"; };
 		CD217D921CCAD885007C50C6 /* json_common.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json_common.hpp; sourceTree = "<group>"; };
 		CD2B09881E6374F300D6D23A /* json_binder_terminate.t.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = json_binder_terminate.t.h; sourceTree = "<group>"; };
 		CD2B098A1E63822100D6D23A /* json_direct_get_binder.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json_direct_get_binder.hpp; sourceTree = "<group>"; };
 		CD2B098B1E63839A00D6D23A /* json_binder_tuple.t.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = json_binder_tuple.t.h; sourceTree = "<group>"; };
+		CD31E1E9219CE85A001C2AF1 /* json_binder_polymorphic.t.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = json_binder_polymorphic.t.h; sourceTree = "<group>"; };
 		CD3C80CF1D6A711000ACC795 /* libjson-direct.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libjson-direct.dylib"; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD472C751CCC1ABD0084C8D6 /* json_common.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = json_common.cpp; sourceTree = "<group>"; };
 		CD472C791CCC1CD80084C8D6 /* json_tuple_binder.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json_tuple_binder.hpp; sourceTree = "<group>"; };
@@ -146,6 +149,8 @@
 				CD472C7B1CCC1DA20084C8D6 /* json_direct_map_binder.hpp */,
 				CD472C7D1CCC1E120084C8D6 /* json_direct_scalar_binder.hpp */,
 				CD472C7C1CCC1DDF0084C8D6 /* json_direct_vector_binder.hpp */,
+				CD1AD696219E60DC00AB8959 /* json_pointer_binder.hpp */,
+				CD1AD697219E615700AB8959 /* json_polymorphic_binder.hpp */,
 			);
 			name = impl;
 			sourceTree = "<group>";
@@ -193,6 +198,7 @@
 				CDECC7D41E64E6A900BEE842 /* json_binder_collection.t.h */,
 				CD2B09881E6374F300D6D23A /* json_binder_terminate.t.h */,
 				CD84C4EB1F68908F002014D3 /* json_binder_custom.t.h */,
+				CD31E1E9219CE85A001C2AF1 /* json_binder_polymorphic.t.h */,
 				CD679D781E6126C700F9F843 /* json_tc.cpp */,
 			);
 			name = test;
@@ -417,13 +423,14 @@
 				"$(SRCROOT)/json_binder_test_bool.t.h",
 				"$(SRCROOT)/json_binder_object.t.h",
 				"$(SRCROOT)/json_binder_custom.t.h",
+				"$(SRCROOT)/json_binder_polymorphic.t.h",
 			);
 			outputPaths = (
 				"$(SRCDIR)/json_tc.cpp",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "cxxtestgen --error-printer -o json_tc.cpp json_binder_value_int.t.h json_binder_value_string.t.h json_binder_value_double.t.h json_binder_terminate.t.h json_binder_tuple.t.h json_binder_collection.t.h json_binder_test_bool.t.h json_binder_object.t.h json_binder_custom.t.h";
+			shellScript = "cxxtestgen --error-printer -o json_tc.cpp json_binder_value_int.t.h json_binder_value_string.t.h json_binder_value_double.t.h json_binder_terminate.t.h json_binder_tuple.t.h json_binder_collection.t.h json_binder_test_bool.t.h json_binder_object.t.h json_binder_custom.t.h json_binder_polymorphic.t.h\n";
 		};
 /* End PBXShellScriptBuildPhase section */
 

+ 32 - 0
json/json_pointer_binder.hpp

@@ -0,0 +1,32 @@
+//
+//  json_pointer_binder.hpp
+//  json
+//
+//  Created by Sam Jaffe on 11/15/18.
+//  Copyright © 2018 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+namespace json { namespace binder {
+  template <typename P>
+  class pointer_binder : public binder_impl<P> {
+    using T = typename std::remove_reference<decltype(*std::declval<P>())>::type;
+  public:
+    pointer_binder(binder<T> impl) : m_impl(impl) {}
+    
+    virtual binder_impl<P>* clone() const { return new pointer_binder(*this); }
+    
+    virtual void parse(P & v, char const*& data, parser::options opts) const {
+      if (!strncmp(data, "null", 4)) v = nullptr;
+      else m_impl.parse(*(v = P(new T)), data, opts);
+    }
+    
+    void write(P const & v, std::ostream & os) const {
+      if (v) m_impl.write(*v, os);
+      else os << "null";
+    }
+  private:
+    binder<T> m_impl;
+  };
+} }

+ 105 - 0
json/json_polymorphic_binder.hpp

@@ -0,0 +1,105 @@
+//
+//  json_polymorphic_binder.hpp
+//  json
+//
+//  Created by Sam Jaffe on 11/15/18.
+//  Copyright © 2018 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+namespace json { namespace binder {
+  template <typename PtrBase, typename Derived>
+  class polymorphic_pointer_binder : public binder_impl<PtrBase> {
+  public:
+    polymorphic_pointer_binder(binder<Derived> impl) : m_impl(impl) {}
+    virtual binder_impl<PtrBase>* clone() const { return new polymorphic_pointer_binder(*this); }
+    
+    virtual void parse(PtrBase & v, char const*& data, parser::options opts) const {
+      if (!strncmp(data, "null", 4)) v = nullptr;
+      else m_impl.parse(reinterpret_cast<Derived &>(*(v = PtrBase(new Derived))), data, opts);
+    }
+    
+    void write(PtrBase const & v, std::ostream & os) const {
+      if (v) m_impl.write(reinterpret_cast<Derived const &>(*v), os);
+      else os << "null";
+    }
+  private:
+    binder<Derived> m_impl;
+  };
+  
+  template <typename Ptr>
+  class polymorphic_binder : public binder_impl<Ptr> {
+  private:
+    using Base = typename std::remove_reference<decltype(*std::declval<Ptr>())>::type;
+    static_assert(std::is_polymorphic<Base>::value, "Must use a polymorphic type");
+  public:
+    polymorphic_binder() = default;
+    virtual binder_impl<Ptr>* clone() const override { return new polymorphic_binder(*this); }
+    
+    template <typename Derived>
+    polymorphic_binder& operator()(std::string const&k, binder_impl<Derived> const&v) {
+      mapping.emplace(k, polymorphic_pointer_binder<Ptr, Derived>(v));
+      reverse_lookup.emplace(k, [](Base const * p) { return dynamic_cast<Derived const *>(p); });
+      return *this;
+    }
+    
+    virtual void parse(Ptr & object, char const*& data, parser::options opts) const override {
+      const char ch = json::helper::get_next_element(data);
+      if (ch == '{') {
+        parse_object(object, ++data, opts);
+      } else {
+        throw json::malformed_json_exception(std::string("Expected an object type for binding to ") + typeid(Base).name());
+      }
+    }
+    virtual void write(Ptr const & val, std::ostream & data) const override {
+      data << '{';
+      using pair_t = typename decltype(reverse_lookup)::value_type;
+      Base const * p = std::addressof(*val);
+      auto it = std::find_if(reverse_lookup.begin(), reverse_lookup.end(),
+                             [p](pair_t const & pair) { return pair.second(p); });
+      if (it == reverse_lookup.end()) {
+        throw std::domain_error("Unknown JSON binding object");
+      }
+      data << "\"@id\":\"" << it->first << "\",";
+      data << "\"@value\":";
+      mapping.find(it->first)->second.write(val, data);
+      data << '}';
+    }
+    
+    void parse_object(Ptr & object, char const*& data, parser::options opts) const {
+      std::string key;
+      fetch_expected_key("@id", data);
+      if (json::helper::get_next_element(++data) != '"') {
+        throw json::malformed_json_exception(std::string("Expected polymorphic id starting with '\"', got '") + *data + "' instead");
+      }
+      json::helper::parse_string(key, data);
+      auto it = mapping.find(key);
+      if (it == mapping.end()) {
+        throw json::malformed_json_exception(std::string("Unknown polymorphic type-id: '") + key + "'");
+      }
+      fetch_expected_key("@value", ++data);
+      it->second.parse(object, ++data, opts);
+      if (!*data) throw json::unterminated_json_exception("Reached end of parse string without finding object end");
+      else if (*data != '}') throw json::malformed_json_exception(std::string("Unexpected non-terminated object '") + *data + "'");
+      else ++data;
+    }
+    
+    void fetch_expected_key(std::string const & expected, char const*& data) const {
+      std::string key;
+      if (json::helper::get_next_element(data) != '"') {
+        throw json::malformed_json_exception(std::string("Expected object key starting with '\"', got '") + *data + "' instead");
+      }
+      json::helper::parse_string(key, data);
+      if (key != expected) {
+        throw json::malformed_json_exception(std::string("Expecting '") + expected + "' polymorphic token, got '" + key + "' instead");
+      }
+      if (json::helper::get_next_element(data) != ':') {
+        throw json::malformed_json_exception(std::string("Expected key:value pair delimited by ':', got '") + *data + "' instead");
+      }
+    }
+  private:
+    std::map<std::string, std::function<bool(Base const*)>> reverse_lookup;
+    std::map<std::string, binder<Ptr>> mapping;
+  };
+} }

+ 2 - 0
json_binder.hpp

@@ -106,5 +106,7 @@ namespace json {
 #include "json/json_direct_map_binder.hpp"
 #include "json/json_direct_scalar_binder.hpp"
 #include "json/json_direct_vector_binder.hpp"
+#include "json/json_pointer_binder.hpp"
+#include "json/json_polymorphic_binder.hpp"
 
 #endif /* json_binder_h */

+ 84 - 0
json_binder_polymorphic.t.h

@@ -0,0 +1,84 @@
+//
+//  json_binder_polymorphic.t.h
+//  json
+//
+//  Created by Sam Jaffe on 11/14/18.
+//  Copyright © 2018 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <cxxtest/TestSuite.h>
+#include "json_binder.hpp"
+
+struct Base {
+  virtual ~Base() = default;
+  virtual void run() const = 0;
+};
+
+struct Left : public Base {
+  int a;
+  void run() const override {}
+};
+
+struct Right : public Base {
+  std::string a;
+  void run() const override {}
+};
+
+bool operator==(Left const & lhs, Left const & rhs) {
+  return lhs.a == rhs.a;
+}
+
+bool operator==(Right const & lhs, Right const & rhs) {
+  return lhs.a == rhs.a;
+}
+
+using namespace json;
+using namespace json::binder;
+object_binder<Left> GetLeftBinder() {
+  return object_binder<Left>()
+  ("a", &Left::a);
+}
+
+object_binder<Right> GetRightBinder() {
+  return object_binder<Right>()
+  ("a", &Right::a);
+}
+
+using pBase = std::unique_ptr<Base>;
+
+polymorphic_binder<pBase> GetBinder() {
+  return polymorphic_binder<pBase>()
+  ("Left", GetLeftBinder())
+  ("Right", GetRightBinder());
+}
+
+polymorphic_binder<pBase> & Get() {
+  static polymorphic_binder<pBase> _ = GetBinder();
+  return _;
+}
+
+using namespace json::parser;
+class json_binder_polymorphic_TestSuite : public CxxTest::TestSuite {
+  public:
+  void testCanHitLeftPolymorph() {
+    char data[] = "{\"@id\":\"Left\", \"@value\":{\"a\":1}}";
+    pBase out;
+    Left expected; expected.a = 1;
+    TS_ASSERT_THROWS_NOTHING(parse(json::binder::bind(out, Get()), data));
+    TS_ASSERT_DIFFERS(&*out, nullptr);
+    TS_ASSERT(dynamic_cast<Left*>(&*out));
+    TS_ASSERT_EQUALS(dynamic_cast<Left&>(*out), expected);
+  }
+
+  void testCanHitRightPolymorph() {
+    char data[] = "{\"@id\":\"Right\", \"@value\":{\"a\":\"hello\"}}";
+    pBase out;
+    Right expected; expected.a = "hello";
+    TS_ASSERT_THROWS_NOTHING(parse(json::binder::bind(out, Get()), data));
+    TS_ASSERT_DIFFERS(&*out, nullptr);
+    TS_ASSERT(dynamic_cast<Right*>(&*out));
+    TS_ASSERT_EQUALS(dynamic_cast<Right&>(*out), expected);
+  }
+};