Browse Source

Extending code coverage to reach most cases.

Sam Jaffe 7 năm trước cách đây
mục cha
commit
3da32490a6

+ 3 - 2
include/json/binder/json_object_binder.hpp

@@ -85,10 +85,11 @@ namespace json { namespace binder {
         }
         json::helper::advance_to_boundary('}', data);
       }
-      if (*data)
+      if (*data) {
         ++data;
-      else
+      } else {
         throw unterminated_json_object();
+      }
       if (!unparsed_keys.empty() && opts & parser::disable_missing_keys) {
         throw json::malformed_json_exception{
             "missing certain keys from object construction TODO"};

+ 28 - 24
include/json/binder/json_polymorphic_binder.hpp

@@ -25,20 +25,13 @@ namespace json { namespace binder {
 
     virtual void parse(PtrBase & v, char const *& data,
                        parser::options opts) const {
-      if (!strncmp(data, "null", 4))
-        v = nullptr;
-      else {
-        v = PtrBase(new Derived);
-        Derived & actual = dynamic_cast<Derived &>(*v);
-        m_impl.parse(actual, data, opts);
-      }
+      v = PtrBase(new Derived);
+      Derived & actual = dynamic_cast<Derived &>(*v);
+      m_impl.parse(actual, data, opts);
     }
 
     void write(PtrBase const & v, std::ostream & os) const {
-      if (v)
-        m_impl.write(dynamic_cast<Derived const &>(*v), os);
-      else
-        os << "null";
+      m_impl.write(dynamic_cast<Derived const &>(*v), os);
     }
 
   private:
@@ -73,26 +66,22 @@ namespace json { namespace binder {
       const char ch = json::helper::get_next_element(data);
       if (ch == '{') {
         parse_object(object, ++data, opts);
+      } else if (!strncmp(data, "null", 4)) {
+        object = nullptr;
       } else {
         throw not_an_object_exception(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");
+      if (!val) {
+        data << "null";
+      } else {
+        write_object(val, data);
       }
-      data << "\"@id\":\"" << it->first << "\",";
-      data << "\"@value\":";
-      mapping.find(it->first)->second.write(val, data);
-      data << '}';
     }
 
+  private:
     void parse_object(Ptr & object, char const *& data,
                       parser::options opts) const {
       std::string key;
@@ -119,6 +108,22 @@ namespace json { namespace binder {
         ++data;
     }
 
+    void write_object(Ptr const & val, std::ostream & data) const {
+      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 fetch_expected_key(std::string const & expected,
                             char const *& data) const {
       std::string key;
@@ -136,7 +141,6 @@ namespace json { namespace binder {
       }
     }
 
-  private:
     std::map<std::string, std::function<bool(Base const *)>> reverse_lookup;
     std::map<std::string, binder<Ptr>> mapping;
   };

+ 8 - 9
test/json_binder_collection_test.cxx

@@ -5,8 +5,8 @@
 //  Created by Sam Jaffe on 2/27/17.
 //
 
-#include <gmock/gmock.h>
 #include "json/json_binder.hpp"
+#include <gmock/gmock.h>
 
 using namespace json::binder;
 using namespace json::parser;
@@ -26,7 +26,7 @@ TEST(JsonBinderArrayTest, ThrowsOnMissingEndToken) {
   collect out;
   value_binder<collect> binder{};
   EXPECT_THROW(parse(json::binder::bind(out, binder), data, allow_all),
-                   json::unterminated_json_exception);
+               json::unterminated_json_exception);
 }
 
 TEST(JsonBinderArrayTest, ThrowsOnMissingStartToken) {
@@ -35,10 +35,9 @@ TEST(JsonBinderArrayTest, ThrowsOnMissingStartToken) {
   collect out;
   value_binder<collect> binder{};
   EXPECT_THROW(parse(json::binder::bind(out, binder), data, allow_all),
-                   json::malformed_json_exception);
+               json::malformed_json_exception);
 }
 
-
 TEST(JsonBinderMapTest, ParsesSuccessfully) {
   char data[] = "{ \"a\" : 1 , \"b\" : 2 }";
   using collect = std::map<std::string, int>;
@@ -54,7 +53,7 @@ TEST(JsonBinderMapTest, ThrowsOnMissingEndToken) {
   collect out;
   value_binder<collect> binder{};
   EXPECT_THROW(parse(json::binder::bind(out, binder), data, allow_all),
-                   json::unterminated_json_exception);
+               json::unterminated_json_exception);
 }
 
 TEST(JsonBinderMapTest, ThrowsOnMissingValueForKey) {
@@ -63,7 +62,7 @@ TEST(JsonBinderMapTest, ThrowsOnMissingValueForKey) {
   collect out;
   value_binder<collect> binder{};
   EXPECT_THROW(parse(json::binder::bind(out, binder), data, allow_all),
-                   json::malformed_json_exception);
+               json::malformed_json_exception);
 }
 
 TEST(JsonBinderMapTest, ThrowsOnMissingStartToken) {
@@ -72,14 +71,14 @@ TEST(JsonBinderMapTest, ThrowsOnMissingStartToken) {
   collect out;
   value_binder<collect> binder{};
   EXPECT_THROW(parse(json::binder::bind(out, binder), data, allow_all),
-                   json::malformed_json_exception);
+               json::malformed_json_exception);
 }
 
 TEST(JsonBinderArrayTest, WritesDataWithoutWhitespace) {
   std::string const expected = "[5,4,7]";
   std::stringstream ss;
   using collect = std::vector<int>;
-  collect const in{ 5, 4, 7 };
+  collect const in{5, 4, 7};
   value_binder<collect> binder{};
   EXPECT_NO_THROW(write(json::binder::bind(in, binder), ss));
   EXPECT_THAT(ss.str(), expected);
@@ -89,7 +88,7 @@ TEST(JsonBinderMapTest, WritesDataWithoutWhitespace) {
   std::string const expected = "{\"a\":1,\"b\":2}";
   std::stringstream ss;
   using collect = std::map<std::string, int>;
-  collect const in{ { "a", 1 }, { "b", 2 } };
+  collect const in{{"a", 1}, {"b", 2}};
   value_binder<collect> binder{};
   EXPECT_NO_THROW(write(json::binder::bind(in, binder), ss));
   EXPECT_THAT(ss.str(), expected);

+ 10 - 10
test/json_binder_custom_test.cxx

@@ -5,11 +5,11 @@
 //  Created by Sam Jaffe on 9/12/17.
 //
 
-#include <gmock/gmock.h>
 #include "json/json_binder.hpp"
+#include <gmock/gmock.h>
 
-#include <iomanip>
 #include <cmath>
+#include <iomanip>
 
 using namespace json::binder;
 using namespace json::parser;
@@ -18,20 +18,21 @@ class precision_binder : public binder_impl<double> {
 public:
   precision_binder(int d) : digits(d), trunc(std::pow(10, d)) {}
   virtual ~precision_binder() = default;
-  
+
   binder_impl<double> * clone() const override {
     return new precision_binder{*this};
   }
-  
-  void parse(double & value, char const * & str, options) const override {
+
+  void parse(double & value, char const *& str, options) const override {
     std::sscanf(str, "%lf", &value);
     value = static_cast<long long>(value * trunc) / trunc;
     json::parse_discard_token(str); // Advance to the boundary
   }
-  
+
   void write(double const & value, std::ostream & out) const override {
     out << std::fixed << std::setprecision(digits) << value;
   }
+
 private:
   int digits;
   double trunc;
@@ -50,10 +51,9 @@ bool operator==(CustomObject const & lhs, CustomObject const & rhs) {
 class JsonBinderCustomTest : public ::testing::Test {
 protected:
   static object_binder<CustomObject> & GetBinder() {
-    static object_binder<CustomObject> val = object_binder<CustomObject>()
-    ("A", &CustomObject::A, precision_binder(4))
-    ("B", &CustomObject::B, precision_binder(2))
-    ("C", &CustomObject::C);
+    static object_binder<CustomObject> val = object_binder<CustomObject>()(
+        "A", &CustomObject::A, precision_binder(4))(
+        "B", &CustomObject::B, precision_binder(2))("C", &CustomObject::C);
     return val;
   }
 };

+ 57 - 45
test/json_binder_object_test.cxx

@@ -5,8 +5,8 @@
 //  Created by Sam Jaffe on 2/27/17.
 //
 
-#include <gmock/gmock.h>
 #include "json/json_binder.hpp"
+#include <gmock/gmock.h>
 
 using namespace json::binder;
 using namespace json::parser;
@@ -21,115 +21,127 @@ struct TestObject {
 };
 
 bool operator==(TestObject const & lhs, TestObject const & rhs) {
-  return lhs.count == rhs.count
-  && lhs.average == rhs.average
-  && lhs.data == rhs.data;
+  return lhs.count == rhs.count && lhs.average == rhs.average &&
+         lhs.data == rhs.data;
 }
 
 class JsonBinderObjectTest : public ::testing::Test {
 protected:
   static object_binder<TestObject> & GetBinder() {
-    static object_binder<TestObject> val = object_binder<TestObject>()
-    ("count", &TestObject::count)
-    ("average", &TestObject::average)
-    ("data", direct_binder<TestObject, map_t>(&TestObject::data));
+    static object_binder<TestObject> val = object_binder<TestObject>()(
+        "count", &TestObject::count)("average", &TestObject::average)(
+        "data", direct_binder<TestObject, map_t>(&TestObject::data));
     return val;
   }
 };
 
 TEST_F(JsonBinderObjectTest, ParsesSuccessfully) {
   char data[] = "{ \"count\":10, \"average\":1.0, "
-  "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }}";
-  TestObject out = { 0, 0.0, {} };
-  TestObject expected = { 10, 1.0, {{"key1", {1, 2}},{"key2", {3, 4}}} };
+                "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }}";
+  TestObject out = {0, 0.0, {}};
+  TestObject expected = {10, 1.0, {{"key1", {1, 2}}, {"key2", {3, 4}}}};
   EXPECT_NO_THROW(parse(json::binder::bind(out, GetBinder()), data, allow_all));
   EXPECT_THAT(out, expected);
 }
 
 TEST_F(JsonBinderObjectTest, IgnoresUnknownKeysByDefault) {
   char data[] = "{ \"count\":10, \"average\":1.0, "
-  "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, "
-  "\"lemon\":true}";
-  TestObject out = { 0, 0.0, {} };
-  TestObject expected = { 10, 1.0, {{"key1", {1, 2}},{"key2", {3, 4}}} };
+                "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, "
+                "\"lemon\":true}";
+  TestObject out = {0, 0.0, {}};
+  TestObject expected = {10, 1.0, {{"key1", {1, 2}}, {"key2", {3, 4}}}};
   EXPECT_NO_THROW(parse(json::binder::bind(out, GetBinder()), data, allow_all));
   EXPECT_THAT(out, expected);
 }
 
 TEST_F(JsonBinderObjectTest, ThrowsOnUnknownKeyWithSetting) {
   char data[] = "{ \"count\":10, \"average\":1.0, "
-  "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, "
-  "\"lemon\":true}";
-  TestObject out = { 0, 0.0, {} };
-  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data,
-                     disable_unknown_keys),
-               json::malformed_json_exception);
+                "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, "
+                "\"lemon\":true}";
+  TestObject out = {0, 0.0, {}};
+  EXPECT_THROW(
+      parse(json::binder::bind(out, GetBinder()), data, disable_unknown_keys),
+      json::malformed_json_exception);
 }
 
 TEST_F(JsonBinderObjectTest, AllowsMissingExpectedKeysByDefault) {
   char data[] = "{ \"count\":10, \"average\":1.0 }";
-  TestObject out = { 0, 0.0, {} };
-  TestObject expected = { 10, 1.0, {} };
+  TestObject out = {0, 0.0, {}};
+  TestObject expected = {10, 1.0, {}};
   EXPECT_NO_THROW(parse(json::binder::bind(out, GetBinder()), data, allow_all));
   EXPECT_THAT(out, expected);
 }
 
 TEST_F(JsonBinderObjectTest, ThrowsOnMissingExpectedKeyWithSetting) {
   char data[] = "{ \"count\":10, \"average\":1.0 }";
-  TestObject out = { 0, 0.0, {} };
-  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data,
-                     disable_missing_keys),
+  TestObject out = {0, 0.0, {}};
+  EXPECT_THROW(
+      parse(json::binder::bind(out, GetBinder()), data, disable_missing_keys),
+      json::malformed_json_exception);
+}
+
+TEST_F(JsonBinderObjectTest, ThrowsMalformedObjectKey) {
+  char data[] = "{ count\":10, \"average\":1.0 }";
+  TestObject out = {0, 0.0, {}};
+  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data, allow_all),
+               json::malformed_json_exception);
+}
+
+TEST_F(JsonBinderObjectTest, ThrowsMalformedObjectAssoc) {
+  char data[] = "{ \"count\"=10, \"average\":1.0 }";
+  TestObject out = {0, 0.0, {}};
+  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data, allow_all),
                json::malformed_json_exception);
 }
 
 TEST_F(JsonBinderObjectTest, ThrowsOnMissingStartToken) {
   char data[] = "\"count\":10, \"average\":1.0, "
-  "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] } }";
-  TestObject out = { 0, 0.0, {} };
+                "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] } }";
+  TestObject out = {0, 0.0, {}};
   EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data, allow_all),
                json::malformed_json_exception);
 }
 
 TEST_F(JsonBinderObjectTest, ThrowsIfMissingValueForKey) {
   char data[] = "{ \"count\":10, \"average\":1.0, \"data\": }";
-  TestObject out = { 0, 0.0, {} };
+  TestObject out = {0, 0.0, {}};
   EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data, allow_all),
                json::malformed_json_exception);
 }
 
 TEST_F(JsonBinderObjectTest, ThrowsOnMissingEndToken) {
   char data[] = "{ \"count\":10, \"average\":1.0, "
-  "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }";
-  TestObject out = { 0, 0.0, {} };
+                "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }";
+  TestObject out = {0, 0.0, {}};
   EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data, allow_all),
-                   json::unterminated_json_exception);
+               json::unterminated_json_exception);
 }
 
 TEST_F(JsonBinderObjectTest, ThrowsOnMissingValueForKeyEvenForUnknownKey) {
   char data[] = "{ \"count\":10, \"average\":1.0, "
-  "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, "
-  "\"lemon\":{\"key\": } }";
-  TestObject out = { 0, 0.0, {} };
+                "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, "
+                "\"lemon\":{\"key\": } }";
+  TestObject out = {0, 0.0, {}};
   EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data, allow_all),
                json::malformed_json_exception);
 }
 
 TEST_F(JsonBinderObjectTest, ThrowsOnMissingEndTokenEvenForUnknownKey) {
   char data[] = "{ \"count\":10, \"average\":1.0, "
-  "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, "
-  "\"lemon\":[1.0 }";
-  TestObject out = { 0, 0.0, {} };
+                "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, "
+                "\"lemon\":[1.0 }";
+  TestObject out = {0, 0.0, {}};
   EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data, allow_all),
-                   json::unterminated_json_exception);
+               json::unterminated_json_exception);
 }
 
 TEST_F(JsonBinderObjectTest, ThrowsOnMissingEndTokenEvenForUnknownKey2) {
   char data[] = "{ \"count\":10, \"average\":1.0, "
-  "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, "
-  "\"lemon\":{\"key\":false }";
-  TestObject out = { 0, 0.0, {} };
+                "\"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, "
+                "\"lemon\":{\"key\":false }";
+  TestObject out = {0, 0.0, {}};
   EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data, allow_all),
-                   json::unterminated_json_exception);
+               json::unterminated_json_exception);
 }
 
 std::string ify(double value) {
@@ -141,9 +153,9 @@ std::string ify(double value) {
 TEST_F(JsonBinderObjectTest, WritesBackOriginalObject) {
   std::string const dbl = ify(1.0);
   std::string const expected = "{\"average\":1.000000,\"count\":10,"
-  "\"data\":{\"key1\":[1,2],\"key2\":[3,4]}}";
+                               "\"data\":{\"key1\":[1,2],\"key2\":[3,4]}}";
   std::stringstream ss;
-  TestObject const in = { 10, 1.0, { { "key1", {1, 2} }, { "key2", {3, 4} } } };
+  TestObject const in = {10, 1.0, {{"key1", {1, 2}}, {"key2", {3, 4}}}};
   EXPECT_NO_THROW(write(json::binder::bind(in, GetBinder()), ss));
   EXPECT_THAT(ss.str(), expected);
 }

+ 159 - 19
test/json_binder_polymorphic_test.cxx

@@ -6,8 +6,8 @@
 //  Copyright © 2018 Sam Jaffe. All rights reserved.
 //
 
-#include <gmock/gmock.h>
 #include "json/json_binder.hpp"
+#include <gmock/gmock.h>
 
 struct Base {
   virtual ~Base() = default;
@@ -24,13 +24,14 @@ struct Right : public Base {
   void run() const override {}
 };
 
-bool operator==(Left const & lhs, Left const & rhs) {
-  return lhs.a == rhs.a;
-}
+struct Middle : public Base {
+  std::tuple<int, std::string> a;
+  void run() const override {}
+};
 
-bool operator==(Right const & lhs, Right const & rhs) {
-  return lhs.a == rhs.a;
-}
+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::binder;
 using namespace json::parser;
@@ -40,16 +41,15 @@ protected:
   using pBase = std::unique_ptr<Base>;
 
   polymorphic_binder<pBase> & GetBinder() {
-    static polymorphic_binder<pBase> val = polymorphic_binder<pBase>()
-    ("Left", object_binder<Left>()("a", &Left::a))
-    ("Right", object_binder<Right>()("a", &Right::a));
+    static polymorphic_binder<pBase> val = polymorphic_binder<pBase>()(
+        "Left", object_binder<Left>()("a", &Left::a))(
+        "Right", object_binder<Right>()("a", &Right::a));
     return val;
   }
 };
 
-template <typename T>
-bool instanceof(Base * ptr) {
-  return dynamic_cast<T*>(ptr) != nullptr;
+template <typename T> bool instanceof (Base * ptr) {
+  return dynamic_cast<T *>(ptr) != nullptr;
 }
 
 using namespace ::testing;
@@ -57,19 +57,159 @@ using namespace ::testing;
 TEST_F(JsonBinderPolymorphicTest, CanHitLeftPolymorph) {
   char data[] = "{\"@id\":\"Left\", \"@value\":{\"a\":1}}";
   pBase out;
-  Left expected; expected.a = 1;
+  Left expected;
+  expected.a = 1;
   EXPECT_NO_THROW(parse(json::binder::bind(out, GetBinder()), data));
   EXPECT_THAT(out, Not(IsNull()));
-  EXPECT_PRED1(instanceof<Left>, out.get());
-  EXPECT_THAT(dynamic_cast<Left&>(*out), expected);
+  EXPECT_PRED1(instanceof <Left>, out.get());
+  EXPECT_THAT(dynamic_cast<Left &>(*out), expected);
 }
 
 TEST_F(JsonBinderPolymorphicTest, CanHitRightPolymorph) {
   char data[] = "{\"@id\":\"Right\", \"@value\":{\"a\":\"hello\"}}";
   pBase out;
-  Right expected; expected.a = "hello";
+  Right expected;
+  expected.a = "hello";
   EXPECT_NO_THROW(parse(json::binder::bind(out, GetBinder()), data));
   EXPECT_THAT(out, Not(IsNull()));
-  EXPECT_PRED1(instanceof<Right>, out.get());
-  EXPECT_THAT(dynamic_cast<Right&>(*out), expected);
+  EXPECT_PRED1(instanceof <Right>, out.get());
+  EXPECT_THAT(dynamic_cast<Right &>(*out), expected);
+}
+
+TEST_F(JsonBinderPolymorphicTest, ThrowsOnUnknownSubclass) {
+  char data[] = "{\"@id\":\"Middle\", \"@value\":{\"a\":[]]}}";
+  pBase out;
+  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data),
+               json::malformed_json_exception);
+  EXPECT_THAT(out, IsNull());
+}
+
+TEST_F(JsonBinderPolymorphicTest, ThrowsOnIdNotString) {
+  char data[] = "{\"@id\":0, \"@value\":{\"a\":[]]}}";
+  pBase out;
+  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data),
+               json::malformed_json_exception);
+  EXPECT_THAT(out, IsNull());
+}
+
+TEST_F(JsonBinderPolymorphicTest, ThrowsOnUnexpectedKey1) {
+  char data[] = "{\"@class\":\"Left\", \"@value\":{\"a\":[]]}}";
+  pBase out;
+  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data),
+               json::malformed_json_exception);
+  EXPECT_THAT(out, IsNull());
+}
+
+TEST_F(JsonBinderPolymorphicTest, ThrowsOnUnexpectedKey2) {
+  char data[] = "{\"@id\":\"Left\", \"@impl\":null, \"@value\":{\"a\":[]]}}";
+  pBase out;
+  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data),
+               json::malformed_json_exception);
+  EXPECT_THAT(out, IsNull());
+}
+
+TEST_F(JsonBinderPolymorphicTest, ThrowsOnUnexpectedKey3) {
+  char data[] = "{\"@id\":\"Left\", \"@impl\":{\"a\":[]]}}";
+  pBase out;
+  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data),
+               json::malformed_json_exception);
+  EXPECT_THAT(out, IsNull());
+}
+
+TEST_F(JsonBinderPolymorphicTest, ThrowsOnUnterminatedJson) {
+  char data[] = "{\"@id\":\"Right\", \"@value\":{\"a\":\"hello\"}";
+  pBase out;
+  Right expected;
+  expected.a = "hello";
+  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data),
+               json::malformed_json_exception);
+  EXPECT_THAT(out, Not(IsNull()));
+  EXPECT_PRED1(instanceof <Right>, out.get());
+  EXPECT_THAT(dynamic_cast<Right &>(*out), expected);
+}
+
+TEST_F(JsonBinderPolymorphicTest, ThrowsOnMoreData) {
+  char data[] = "{\"@id\":\"Right\", \"@value\":{\"a\":\"hello\"},"
+                " \"key\":null }";
+  pBase out;
+  Right expected;
+  expected.a = "hello";
+  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data),
+               json::malformed_json_exception);
+  EXPECT_THAT(out, Not(IsNull()));
+  EXPECT_PRED1(instanceof <Right>, out.get());
+  EXPECT_THAT(dynamic_cast<Right &>(*out), expected);
+}
+
+TEST_F(JsonBinderPolymorphicTest, ThrowsOnNonObject) {
+  char data[] = "[\"Right\", \"@value\":{\"a\":\"hello\"}]";
+  pBase out;
+  Right expected;
+  expected.a = "hello";
+  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data),
+               json::malformed_json_exception);
+  EXPECT_THAT(out, IsNull());
+}
+
+TEST_F(JsonBinderPolymorphicTest, ThrowsMalformedObjectKey) {
+  char data[] = "{@id\":\"Right\", \"@value\":{\"a\":\"hello\"}}";
+  pBase out;
+  Right expected;
+  expected.a = "hello";
+  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data),
+               json::malformed_json_exception);
+  EXPECT_THAT(out, IsNull());
+}
+
+TEST_F(JsonBinderPolymorphicTest, ThrowsMalformedObjectAssoc) {
+  char data[] = "{\"@id\"=\"Right\", \"@value\":{\"a\":\"hello\"}}";
+  pBase out;
+  Right expected;
+  expected.a = "hello";
+  EXPECT_THROW(parse(json::binder::bind(out, GetBinder()), data),
+               json::malformed_json_exception);
+  EXPECT_THAT(out, IsNull());
+}
+
+TEST_F(JsonBinderPolymorphicTest, WritesRightImplWithoutWhitespace) {
+  std::string const expected = R"({"@id":"Right","@value":{"a":"hello"}})";
+  Right in;
+  in.a = "hello";
+  pBase ptr(new Right(in));
+  std::stringstream ss;
+  EXPECT_NO_THROW(write(json::binder::bind(ptr, GetBinder()), ss));
+  EXPECT_THAT(ss.str(), expected);
+}
+
+TEST_F(JsonBinderPolymorphicTest, WritesLeftImplWithoutWhitespace) {
+  std::string const expected = R"({"@id":"Left","@value":{"a":1}})";
+  Left in;
+  in.a = 1;
+  pBase ptr(new Left(in));
+  std::stringstream ss;
+  EXPECT_NO_THROW(write(json::binder::bind(ptr, GetBinder()), ss));
+  EXPECT_THAT(ss.str(), expected);
+}
+
+TEST_F(JsonBinderPolymorphicTest, ThrowsWhenWritingUnboundImpl) {
+  Middle in;
+  in.a = {1, "hello"};
+  pBase ptr(new Middle(in));
+  std::stringstream ss;
+  EXPECT_THROW(write(json::binder::bind(ptr, GetBinder()), ss),
+               std::domain_error);
+}
+
+TEST_F(JsonBinderPolymorphicTest, ParsesNullSuccessfully) {
+  char const data[] = "null";
+  pBase out;
+  EXPECT_NO_THROW(parse(json::binder::bind(out, GetBinder()), data));
+  EXPECT_THAT(out, IsNull());
+}
+
+TEST_F(JsonBinderPolymorphicTest, WritesNullSuccessfully) {
+  pBase ptr = nullptr;
+  std::stringstream ss;
+  EXPECT_NO_THROW(write(json::binder::bind(ptr, GetBinder()), ss));
+  EXPECT_THAT(ss.str(), "null");
 }

+ 2 - 3
test/json_binder_terminate_test.cxx

@@ -5,8 +5,8 @@
 //  Created by Sam Jaffe on 2/26/17.
 //
 
-#include <gmock/gmock.h>
 #include "json/json_binder.hpp"
+#include <gmock/gmock.h>
 
 using namespace json::binder;
 using namespace json::parser;
@@ -23,8 +23,7 @@ TEST(JsonBinderEndOfStreamTest, ExpectsEoBToOccurAfterAllParsed) {
   char data[] = "10 0";
   int out = 0;
   value_binder<int> binder{};
-  EXPECT_THROW(parse(bind(out, binder), data,
-                     disable_concatenated_json_bodies),
+  EXPECT_THROW(parse(bind(out, binder), data, disable_concatenated_json_bodies),
                json::malformed_json_exception);
 }
 

+ 3 - 3
test/json_binder_test_bool_test.cxx

@@ -5,8 +5,8 @@
 //  Created by Sam Jaffe on 2/27/17.
 //
 
-#include <gmock/gmock.h>
 #include "json/json_binder.hpp"
+#include <gmock/gmock.h>
 
 using namespace json::binder;
 using namespace json::parser;
@@ -17,7 +17,7 @@ TEST(JsonBinderBoolTest, ParsesTrue) {
   bool out = false;
   value_binder<bool> binder{};
   parse(bind(out, binder), data, allow_all);
-  EXPECT_THAT( out, true );
+  EXPECT_THAT(out, true);
 }
 
 TEST(JsonBinderBoolTest, ParsesFalse) {
@@ -25,7 +25,7 @@ TEST(JsonBinderBoolTest, ParsesFalse) {
   bool out = true;
   value_binder<bool> binder{};
   parse(bind(out, binder), data, allow_all);
-  EXPECT_THAT( out, false );
+  EXPECT_THAT(out, false);
 }
 
 TEST(JsonBinderBoolTest, ThrowsOnBadData) {

+ 5 - 2
test/json_binder_tuple_test.cxx

@@ -5,13 +5,16 @@
 //  Created by Sam Jaffe on 2/26/17.
 //
 
-#include <gmock/gmock.h>
 #include "json/json_binder.hpp"
+#include <gmock/gmock.h>
 
 using namespace json::binder;
 using namespace json::parser;
 
-struct point { int x; int y; };
+struct point {
+  int x;
+  int y;
+};
 bool operator==(point const & lhs, point const & rhs) {
   return lhs.x == rhs.x && lhs.y == rhs.y;
 }

+ 2 - 3
test/json_binder_value_double_test.cxx

@@ -5,8 +5,8 @@
 //  Created by Sam Jaffe on 2/25/17.
 //
 
-#include <gmock/gmock.h>
 #include "json/json_binder.hpp"
+#include <gmock/gmock.h>
 
 using namespace json::binder;
 using namespace json::parser;
@@ -47,8 +47,7 @@ TEST(JsonBinderDoubleTest, ThrowsOnTokenConcat) {
   char data[] = "5.0boo";
   double out = 0.0;
   value_binder<double> binder{};
-  EXPECT_THROW(parse(bind(out, binder), data,
-                     disable_concatenated_json_bodies),
+  EXPECT_THROW(parse(bind(out, binder), data, disable_concatenated_json_bodies),
                json::malformed_json_exception);
   EXPECT_THAT(out, 5.0);
 }

+ 1 - 1
test/json_binder_value_int_test.cxx

@@ -5,8 +5,8 @@
 //  Created by Sam Jaffe on 2/24/17.
 //
 
-#include <gmock/gmock.h>
 #include "json/json_binder.hpp"
+#include <gmock/gmock.h>
 
 using namespace json::binder;
 using namespace json::parser;

+ 2 - 2
test/json_binder_value_string_test.cxx

@@ -34,7 +34,7 @@ TEST(JsonBinderStringTest, CannotParseRawString) {
   std::string out = "";
   value_binder<std::string> binder{};
   EXPECT_THROW(parse(json::binder::bind(out, binder), data, allow_all),
-                   json::malformed_json_exception);
+               json::malformed_json_exception);
   EXPECT_THAT(out, "");
 }
 
@@ -43,7 +43,7 @@ TEST(JsonBinderStringTest, ThrowsOnNoEndQuote) {
   std::string out = "";
   value_binder<std::string> binder{};
   EXPECT_THROW(parse(json::binder::bind(out, binder), data, allow_all),
-                   json::unterminated_json_exception);
+               json::unterminated_json_exception);
   EXPECT_THAT(out, "");
 }