Browse Source

Updating vector and map binders to be more generic - supporting non-associative and associative structures, respectively.

Adding test cases for bool, vector, map, and structs.
Adding integer test case for signed(negative) -> unsigned.
Adding more tuple test cases for error conditions.
Samuel Jaffe 8 years ago
parent
commit
018198158f

+ 10 - 1
json.xcodeproj/project.pbxproj

@@ -67,6 +67,9 @@
 		CDB2F7421C5D48090067C2EC /* json.hpp */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.cpp.h; path = json.hpp; sourceTree = "<group>"; tabWidth = 2; };
 		CDB2F7451C5E9BEB0067C2EC /* json_binder_parser.hpp */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.cpp.h; path = json_binder_parser.hpp; sourceTree = "<group>"; tabWidth = 2; };
 		CDB2F7461C5EA2E80067C2EC /* json_binder.hpp */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.cpp.h; path = json_binder.hpp; sourceTree = SOURCE_ROOT; tabWidth = 2; };
+		CDECC7D41E64E6A900BEE842 /* json_binder_collection.t.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = json_binder_collection.t.h; sourceTree = "<group>"; };
+		CDECC7D51E6504C800BEE842 /* json_binder_test_bool.t.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = json_binder_test_bool.t.h; sourceTree = "<group>"; };
+		CDECC7D61E65073E00BEE842 /* json_binder_object.t.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = json_binder_object.t.h; sourceTree = "<group>"; };
 		CDF643321C6E9A8B0016A475 /* json_tc */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = json_tc; sourceTree = BUILT_PRODUCTS_DIR; };
 /* End PBXFileReference section */
 
@@ -148,10 +151,13 @@
 		CD679D751E61266300F9F843 /* test */ = {
 			isa = PBXGroup;
 			children = (
+				CDECC7D51E6504C800BEE842 /* json_binder_test_bool.t.h */,
 				CD679D761E61267300F9F843 /* json_binder_value_int.t.h */,
 				CD679D7B1E61E26000F9F843 /* json_binder_value_string.t.h */,
 				CD679D7C1E6273DB00F9F843 /* json_binder_value_double.t.h */,
 				CD2B098B1E63839A00D6D23A /* json_binder_tuple.t.h */,
+				CDECC7D61E65073E00BEE842 /* json_binder_object.t.h */,
+				CDECC7D41E64E6A900BEE842 /* json_binder_collection.t.h */,
 				CD2B09881E6374F300D6D23A /* json_binder_terminate.t.h */,
 				CD679D781E6126C700F9F843 /* json_tc.cpp */,
 			);
@@ -310,13 +316,16 @@
 				"$(SRCROOT)/json_binder_value_double.t.h",
 				"$(SRCROOT)/json_binder_terminate.t.h",
 				"$(SRCROOT)/json_binder_tuple.t.h",
+				"$(SRCROOT)/json_binder_collection.t.h",
+				"$(SRCROOT)/json_binder_test_bool.t.h",
+				"$(SRCROOT)/json_binder_object.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";
+			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";
 		};
 /* End PBXShellScriptBuildPhase section */
 

+ 21 - 9
json/json_direct_map_binder.hpp

@@ -8,29 +8,30 @@
 #pragma once
 
 namespace json { namespace binder {
-  template <typename T, typename V>
-  class direct_binder<T, std::map<std::string, V> > : public binder_impl<T> {
+  template <typename T, typename V, typename C>
+  class associative_binder : public binder_impl<T> {
   public:
-    direct_binder(std::vector<V> T::*p, binder<V> const&i);
-    virtual binder_impl<T>* clone() const override { return new direct_binder(*this); }
+    associative_binder(C T::*p) : ptr(p), impl(value_binder<V>()) {}
+    associative_binder(C T::*p, binder<V> const & i) : ptr(p), impl(i) {}
+    virtual binder_impl<T>* clone() const override { return new associative_binder(*this); }
     
-    virtual void parse(T& val, char const*& data) const override {
+    virtual void parse(T& val, char const*& data, parser::options opts) const override {
       const char ch = json::helper::get_next_element(data);
       if (ch != '{') {
-        throw json::malformed_json_exception("Expected an array type");
+        throw json::malformed_json_exception("Expected an object type");
       }
       ++data;
       
       V to_make;
-      std::map<std::string, V>& vec = val.*ptr;
+      C & map = val.*ptr;
       std::string key;
       while (*data && *data != '}') {
         json::helper::parse_string(key, data);
         if (json::helper::get_next_element(data) != ':') {
           throw json::malformed_json_exception(std::string("Expected key:value pair delimited by ':', got '") + *data + "' instead");
         }
-        impl.parse(to_make, ++data);
-        vec.emplace(key, to_make);
+        impl.parse(to_make, ++data, opts);
+        map.emplace(key, std::move(to_make));
         json::helper::advance_to_boundary('}', data);
       }
       if (*data) ++data;
@@ -56,4 +57,15 @@ namespace json { namespace binder {
     std::map<std::string, V> T::*ptr;
     binder<V> impl;
   };
+  
+#define ASSOCIATIVE_DIRECT_BINDER( C ) \
+  template <typename T, typename V, typename... O> \
+  class direct_binder<T, C<std::string, V, O...> > : \
+  public associative_binder<T, V, C<std::string, V, O...>> { \
+  public: \
+    using associative_binder<T, V, C<std::string, V, O...>>::associative_binder; \
+  }
+
+  ASSOCIATIVE_DIRECT_BINDER( std::map );
+//  ASSOCIATIVE_DIRECT_BINDER( std::unordered_map );
 } }

+ 19 - 5
json/json_direct_vector_binder.hpp

@@ -8,11 +8,12 @@
 #pragma once
 
 namespace json { namespace binder {
-  template <typename T, typename V>
-  class direct_binder<T, std::vector<V> > : public binder_impl<T> {
+  template <typename T, typename V, typename C>
+  class non_associative_binder : public binder_impl<T> {
   public:
-    direct_binder(std::vector<V> T::*p, binder<V> const&i);
-    virtual binder_impl<T>* clone() const override { return new direct_binder(*this); }
+    non_associative_binder(C T::*p) : ptr(p), impl(value_binder<V>()) {}
+    non_associative_binder(C T::*p, binder<V> const & i) : ptr(p), impl(i) {}
+    virtual binder_impl<T>* clone() const override { return new non_associative_binder(*this); }
     
     virtual void parse(T& val, char const*& data, parser::options opts) const override {
       const char ch = json::helper::get_next_element(data);
@@ -22,7 +23,7 @@ namespace json { namespace binder {
       ++data;
       
       V to_make;
-      std::vector<V>& vec = val.*ptr;
+      C & vec = val.*ptr;
       while (*data && *data != ']') {
         impl.parse(to_make, data, opts);
         vec.emplace_back(to_make);
@@ -49,4 +50,17 @@ namespace json { namespace binder {
     std::vector<V> T::*ptr;
     binder<V> impl;
   };
+  
+#define NON_ASSOCIATIVE_DIRECT_BINDER( C ) \
+  template <typename T, typename V, typename... O> \
+  class direct_binder<T, C<V, O...> > : \
+  public non_associative_binder<T, V, C<V, O...>> { \
+  public: \
+    using non_associative_binder<T, V, C<V, O...>>::non_associative_binder; \
+  }
+  
+  NON_ASSOCIATIVE_DIRECT_BINDER( std::vector );
+  NON_ASSOCIATIVE_DIRECT_BINDER( std::list );
+  NON_ASSOCIATIVE_DIRECT_BINDER( std::set );
+//  NON_ASSOCIATIVE_DIRECT_BINDER( std::unordered_set );
 } }

+ 81 - 0
json_binder_collection.t.h

@@ -0,0 +1,81 @@
+//
+//  json_binder_collection.t.h
+//  json
+//
+//  Created by Sam Jaffe on 2/27/17.
+//
+
+#pragma once
+
+#include <cxxtest/TestSuite.h>
+#include "json_binder.hpp"
+
+using namespace json::binder;
+using namespace json::parser;
+
+class json_binder_collection_TestSuite : public CxxTest::TestSuite {
+public:
+  void test_vector_parsing() {
+    char data[] = "[ 0, 1, 2 ]";
+    using collect = std::vector<int>;
+    collect out;
+    value_binder<collect> binder{value_binder<int>()};
+    TS_ASSERT_THROWS_NOTHING(parse(json::binder::bind(out, binder), data, allow_all));
+    TS_ASSERT_EQUALS(out, collect({0, 1, 2}));
+  }
+  
+  void test_vector_throws_if_array_unterminated() {
+    char data[] = "[ 0, 1";
+    using collect = std::vector<int>;
+    collect out;
+    value_binder<collect> binder{value_binder<int>()};
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, binder), data, allow_all),
+                     json::unterminated_json_exception);
+  }
+  
+  void test_vector_throws_if_array_unstarted() {
+    char data[] = "0, 1";
+    using collect = std::vector<int>;
+    collect out;
+    value_binder<collect> binder{value_binder<int>()};
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, binder), data, allow_all),
+                     json::malformed_json_exception);
+  }
+
+  
+  void test_map_parsing() {
+    char data[] = "{ \"a\" : 1 , \"b\" : 2 }";
+    using collect = std::map<std::string, int>;
+    collect out;
+    value_binder<collect> binder{value_binder<int>()};
+    TS_ASSERT_THROWS_NOTHING(parse(json::binder::bind(out, binder), data, allow_all));
+    TS_ASSERT_EQUALS(out, collect({{"a", 1}, {"b", 2}}));
+  }
+  
+  void test_map_throws_if_object_unterminated() {
+    char data[] = "{ \"a\" : 1 , \"b\" : 2";
+    using collect = std::map<std::string, int>;
+    collect out;
+    value_binder<collect> binder{value_binder<int>()};
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, binder), data, allow_all),
+                     json::unterminated_json_exception);
+  }
+  
+  void test_map_throws_if_object_missing_associative_token() {
+    char data[] = "{ \"a\" : 1 , \"b\" }";
+    using collect = std::map<std::string, int>;
+    collect out;
+    value_binder<collect> binder{value_binder<int>()};
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, binder), data, allow_all),
+                     json::malformed_json_exception);
+  }
+
+  void test_map_throws_if_object_unstarted() {
+    char data[] = "\"a\" : 1 , \"b\" : 2";
+    using collect = std::map<std::string, int>;
+    collect out;
+    value_binder<collect> binder{value_binder<int>()};
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, binder), data, allow_all),
+                     json::malformed_json_exception);
+  }
+};

+ 122 - 0
json_binder_object.t.h

@@ -0,0 +1,122 @@
+//
+//  json_binder_object.t.h
+//  json
+//
+//  Created by Sam Jaffe on 2/27/17.
+//
+
+#pragma once
+
+#include <cxxtest/TestSuite.h>
+#include "json_binder.hpp"
+
+using namespace json::binder;
+using namespace json::parser;
+
+class json_binder_object_TestSuite : public CxxTest::TestSuite {
+public:
+  using vec_t = std::vector<int>;
+  using map_t = std::map<std::string, vec_t>;
+  struct TestObject {
+    int count;
+    double average;
+    std::map<std::string, std::vector<int>> data;
+    bool operator==(TestObject const & other) const {
+      return count == other.count && average == other.average && data == other.data;
+    }
+  };
+  
+  static object_binder<TestObject> GetImpl() {
+    return object_binder<TestObject>()
+      ("count", &TestObject::count)
+      ("average", &TestObject::average)
+      ("data", direct_binder<TestObject, map_t>(&TestObject::data));
+  }
+  
+  static object_binder<TestObject> & Get() {
+    static object_binder<TestObject> val = GetImpl();
+    return val;
+  }
+  
+  void test_construct_object_from_json() {
+    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}}} };
+    TS_ASSERT_THROWS_NOTHING(parse(json::binder::bind(out, Get()), data, allow_all));
+    TS_ASSERT_EQUALS(out, expected);
+  }
+  
+  void test_permits_extra_keys() {
+    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}}} };
+    TS_ASSERT_THROWS_NOTHING(parse(json::binder::bind(out, Get()), data, allow_all));
+    TS_ASSERT_EQUALS(out, expected);
+  }
+  
+  void test_throws_on_extra_keys_with_flag() {
+    char data[] = "{ \"count\":10, \"average\":1.0, \"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, \"lemon\":true}";
+    TestObject out = { 0, 0.0, {} };
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, Get()), data,
+                           disable_unknown_keys),
+                     json::malformed_json_exception);
+  }
+  
+  void test_permits_missing_keys() {
+    char data[] = "{ \"count\":10, \"average\":1.0 }";
+    TestObject out = { 0, 0.0, {} };
+    TestObject expected = { 10, 1.0, {} };
+    TS_ASSERT_THROWS_NOTHING(parse(json::binder::bind(out, Get()), data, allow_all));
+    TS_ASSERT_EQUALS(out, expected);
+  }
+
+  void test_throws_if_missing_keys_with_flag() {
+    char data[] = "{ \"count\":10, \"average\":1.0 }";
+    TestObject out = { 0, 0.0, {} };
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, Get()), data,
+                           disable_missing_keys),
+                     json::malformed_json_exception);
+  }
+  
+  void test_throws_if_object_unstarted() {
+    char data[] = "\"count\":10, \"average\":1.0, \"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] } }";
+    TestObject out = { 0, 0.0, {} };
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, Get()), data, allow_all),
+                     json::malformed_json_exception);
+  }
+  
+  void test_throws_if_object_missing_associative_token() {
+    char data[] = "{ \"count\":10, \"average\":1.0, \"data\": }";
+    TestObject out = { 0, 0.0, {} };
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, Get()), data, allow_all),
+                     json::malformed_json_exception);
+  }
+
+  void test_throws_if_object_unterminated() {
+    char data[] = "{ \"count\":10, \"average\":1.0, \"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }";
+    TestObject out = { 0, 0.0, {} };
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, Get()), data, allow_all),
+                     json::unterminated_json_exception);
+  }
+  
+  void test_throws_if_object_missing_associative_token_discard_t() {
+    char data[] = "{ \"count\":10, \"average\":1.0, \"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, \"lemon\":{\"key\": } }";
+    TestObject out = { 0, 0.0, {} };
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, Get()), data, allow_all),
+                     json::malformed_json_exception);
+  }
+  
+  void test_throws_if_object_unterminated_array_discard_t() {
+    char data[] = "{ \"count\":10, \"average\":1.0, \"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, \"lemon\":[1.0 }";
+    TestObject out = { 0, 0.0, {} };
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, Get()), data, allow_all),
+                     json::unterminated_json_exception);
+  }
+
+  void test_throws_if_object_unterminated_object_discard_t() {
+    char data[] = "{ \"count\":10, \"average\":1.0, \"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, \"lemon\":{\"key\":false }";
+    TestObject out = { 0, 0.0, {} };
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, Get()), data, allow_all),
+                     json::unterminated_json_exception);
+  }
+};

+ 41 - 0
json_binder_test_bool.t.h

@@ -0,0 +1,41 @@
+//
+//  json_binder_test_bool.t.h
+//  json
+//
+//  Created by Sam Jaffe on 2/27/17.
+//
+
+#pragma once
+
+#include <cxxtest/TestSuite.h>
+#include "json_binder.hpp"
+
+using namespace json::binder;
+using namespace json::parser;
+
+class json_binder_value_bool_TestSuite : public CxxTest::TestSuite {
+public:
+  void test_parse_bool_true() {
+    char data[] = "true";
+    bool out = false;
+    value_binder<bool> binder{};
+    parse(bind(out, binder), data, allow_all);
+    TS_ASSERT_EQUALS( out, true );
+  }
+  
+  void test_parse_bool_false() {
+    char data[] = "false";
+    bool out = true;
+    value_binder<bool> binder{};
+    parse(bind(out, binder), data, allow_all);
+    TS_ASSERT_EQUALS( out, false );
+  }
+
+  void test_parse_nonbool_throws() {
+    char data[] = "YES"; // Obj-C
+    bool out = false;
+    value_binder<bool> binder{};
+    TS_ASSERT_THROWS(parse(bind(out, binder), data, allow_all),
+                     json::malformed_json_exception);
+  }
+};

+ 18 - 2
json_binder_tuple.t.h

@@ -31,7 +31,6 @@ public:
     auto binder = make_default_tuple_binder<int, int>();
     TS_ASSERT_THROWS(parse(json::binder::bind(out, binder), data, allow_all),
                      json::malformed_json_exception);
-    TS_ASSERT_EQUALS(out, std::make_tuple(1, 0));
   }
 
   void test_bind_to_tuple_throws_if_too_many_entries() {
@@ -41,7 +40,24 @@ public:
     auto binder = make_default_tuple_binder<int, int>();
     TS_ASSERT_THROWS(parse(json::binder::bind(out, binder), data, allow_all),
                      json::malformed_json_exception);
-    TS_ASSERT_EQUALS(out, std::make_tuple(1, 2));
+  }
+  
+  void test_bind_to_tuple_throws_if_unterminated() {
+    char data[] = "[ 1, 2 ";
+    using tuple = std::tuple<int, int>;
+    tuple out = std::make_tuple(0, 0);
+    auto binder = make_default_tuple_binder<int, int>();
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, binder), data, allow_all),
+                     json::unterminated_json_exception);
+  }
+  
+  void test_bind_to_tuple_throws_if_unstarted() {
+    char data[] = "1, 2 ]";
+    using tuple = std::tuple<int, int>;
+    tuple out = std::make_tuple(0, 0);
+    auto binder = make_default_tuple_binder<int, int>();
+    TS_ASSERT_THROWS(parse(json::binder::bind(out, binder), data, allow_all),
+                     json::malformed_json_exception);
   }
   
   void test_bind_to_tuple_with_multiple_types() {

+ 8 - 0
json_binder_value_int.t.h

@@ -32,6 +32,14 @@ public:
     TS_ASSERT_EQUALS(ss.str(), expected);
   }
   
+  void test_negative_into_unsigned_throws() {
+    char data[] = "-1";
+    unsigned int out = 0;
+    value_binder<unsigned int> binder{};
+    TS_ASSERT_THROWS(parse(bind(out, binder), data, allow_all),
+                     json::json_numeric_exception);
+  }
+  
   void test_empty_buffer_to_int_throws() {
     char data[] = "";
     int out = 0;