Sfoglia il codice sorgente

Doing a lot of cleanup work.
- Move visitor/binder/binder_impl to inner files (a la boost's X.hpp X/... pattern)
- Create some more specialized exceptions
- Start working on keeping everything under 80 chars wide.

Sam Jaffe 7 anni fa
parent
commit
237cc41002

+ 39 - 0
include/json/binder/json_binder.hpp

@@ -0,0 +1,39 @@
+#pragma once
+
+#include "json/json_common.hpp"
+
+#include <iostream>
+
+namespace json {  namespace binder {
+  template <typename T>
+  class binder_impl {
+  public:
+    virtual binder_impl<T>* clone() const = 0;
+    virtual ~binder_impl() {}
+    virtual void parse(T&, char const*&, parser::options) const = 0;
+    virtual void write(T const&, std::ostream &) const = 0;
+  };
+  
+  template <typename T>
+  class binder {
+  public:
+    binder() : impl(nullptr) {}
+    binder(binder const& other) : impl(other.impl->clone()) {}
+    binder(binder_impl<T> const* p) : impl(p) {}
+    binder(binder_impl<T> const& r) : impl(r.clone()) {}
+    
+    ~binder() { delete impl; }
+    
+    void parse(T& object, char const*& data, parser::options opts) const {
+      if (!impl) return; // error?
+      impl->parse(object, data, opts);
+    }
+    
+    void write(T const& object, std::ostream & data) const {
+      if (!impl) return; // error?
+      impl->write(object, data);
+    }
+  private:
+    binder_impl<T> const* impl;
+  };
+} }

+ 6 - 2
include/json/binder/json_binder_parser.hpp

@@ -13,6 +13,8 @@
 #include <iostream>
 #include <memory>
 
+#include "json/json_common.hpp"
+
 namespace json {
   class value;
   
@@ -23,12 +25,14 @@ namespace json {
     void parse(binder::visitor<T, S>&, char const*, options opts = allow_all);
     
     template <typename T, typename S>
-    void parse(binder::visitor<T, S>&& v, std::string const& s, options opts = allow_all) {
+    void parse(binder::visitor<T, S>&& v, std::string const& s,
+               options opts = allow_all) {
       parse(static_cast<binder::visitor<T, S>&>(v), s.c_str(), opts);
     }
     
     template <typename T, typename S>
-    void parse(binder::visitor<T, S>&& v, std::istream & in, options opts = allow_all) {
+    void parse(binder::visitor<T, S>&& v, std::istream & in,
+               options opts = allow_all) {
       if (!in) return;
       in.seekg(0, std::ios_base::end);
       std::istream::pos_type end = in.tellg();

+ 59 - 0
include/json/binder/json_binder_visitor.hpp

@@ -0,0 +1,59 @@
+
+#pragma once
+
+#include "json_binder.hpp"
+#include "json/json_common.hpp"
+
+#include <iosfwd>
+#include <sstream>
+
+namespace json { namespace binder {
+  template <typename T, typename S = T>
+  class visitor {
+  public:
+    visitor(S& o, binder_impl<T>& b) : obj(o), b(b) {}
+
+    void parse(char const* data, parser::options opts) {
+      b.parse(obj, data, opts);
+      if ( json::helper::get_next_element(data)
+          && opts & parser::disable_concatenated_json_bodies ) {
+        throw malformed_json_exception{
+          "Config set to require json input be terminated"
+        };
+      }
+    }
+
+    void write(std::ostream & data) const {
+      b.write(obj, data);
+    }
+
+    private:
+      S& obj;
+      binder_impl<T>& b;
+  };
+
+  template <typename T, typename S>
+  visitor<T, S> bind(S& object, binder_impl<T>& b) {
+    return {object, b};
+  }
+} }
+
+namespace json { namespace parser {
+  template <typename T>
+  void parse(binder::visitor<T>& visitor, char const* data,
+             options opts = allow_all) {
+    visitor.parse(data, opts);
+  }
+
+  template <typename T, typename S>
+  void write(binder::visitor<T, S> const & visitor, std::ostream & out) {
+    visitor.write(out);
+  }
+
+  template <typename T, typename S>
+  void write(binder::visitor<T, S> const & visitor, std::string & data) {
+    std::stringstream ss;
+    visitor.write(ss);
+    data = ss.str();
+  }
+} }

+ 6 - 2
include/json/binder/json_direct_binder.hpp

@@ -7,6 +7,8 @@
 
 #pragma once
 
+#include "json_binder.hpp"
+
 namespace json { namespace binder {
   template <typename T, typename E, typename = void>
   class direct_binder;
@@ -22,7 +24,8 @@ namespace json { namespace binder {
       return new forward_binder(*this);
     }
     
-    virtual void parse(T& val, char const*& data, parser::options opts) const override {
+    virtual void parse(T& val, char const*& data,
+                       parser::options opts) const override {
       impl.parse(val.*ptr, data, opts);
     }
     
@@ -48,7 +51,8 @@ namespace json { namespace binder {
       return new value_binder(*this);
     }
     
-    virtual void parse(T & val, char const *&data, parser::options opts) const override {
+    virtual void parse(T & val, char const *&data,
+                       parser::options opts) const override {
       detail tmp;
       impl.parse(tmp, data, opts);
       val = std::move(tmp.value);

+ 76 - 34
include/json/binder/json_direct_get_binder.hpp

@@ -7,27 +7,35 @@
 
 #pragma once
 
+#include "json_binder.hpp"
+#include "json_direct_binder.hpp"
+#include "json_tuple_binder.hpp"
+
+#include <array>
 #include <tuple>
 
 namespace json { namespace binder {
   template <typename T, std::size_t I>
   class get_binder : public binder_impl<T> {
+    // Maybe a reference type
+    using ith_type = decltype(std::get<I>(std::declval<T>()));
   public:
-    using value_type = typename std::remove_reference<decltype(std::get<I>(std::declval<T>()))>::type;
+    using value_type = typename std::remove_reference<ith_type>::type;
+    
+    get_binder() : impl(value_binder<value_type>()) {}
     
     get_binder(binder<value_type> val_binder)
-    : impl(std::move(val_binder)) {
-      
-    }
+    : impl(std::move(val_binder)) {}
     
-    get_binder(binder_impl<value_type> const & val_binder = value_binder<value_type>())
-    : impl(val_binder) {
-      
-    }
+    get_binder(binder_impl<value_type> const & val_binder)
+    : impl(val_binder) {}
     
-    virtual binder_impl<T>* clone() const override { return new get_binder(*this); }
+    virtual binder_impl<T>* clone() const override {
+      return new get_binder(*this);
+    }
     
-    virtual void parse(T& object, char const*& data, parser::options opts) const override {
+    virtual void parse(T& object, char const*& data,
+                       parser::options opts) const override {
       impl.parse(std::get<I>(object), data, opts);
     }
     
@@ -41,45 +49,63 @@ namespace json { namespace binder {
 #if __cpluscplus >= 201402L
   namespace detail {
     template <typename TupleOut, typename TupleIn, std::size_t... Indexes>
-    tuple_binder<TupleOut> make_tuple_binder(TupleIn && binds, std::index_sequence<Indexes...>) {
+    tuple_binder<TupleOut> make_tuple_binder(TupleIn && binds,
+                                             std::index_sequence<Indexes...>) {
       tuple_binder<TupleOut> binder;
       using swallow = int[];
-      (void) swallow{ 0, (void(binder(get_binder<TupleOut, Indexes>(std::get<Indexes>(binds)))), 0)... };
+      (void) swallow{
+        0,
+        (void(binder(get_binder<TupleOut, Indexes>(std::get<Indexes>(binds)))),
+         0)...
+      };
       return binder;
     }
     
     template <typename TupleOut, typename T, std::size_t... Indexes>
-    tuple_binder<TupleOut> make_array_binder(binder<T> && binds, std::index_sequence<Indexes...>) {
+    tuple_binder<TupleOut> make_array_binder(binder<T> && binds,
+                                             std::index_sequence<Indexes...>) {
       tuple_binder<TupleOut> binder;
       using swallow = int[];
-      (void) swallow{ 0, (void(binder(get_binder<TupleOut, Indexes>(binds))), 0)... };
+      (void) swallow{
+        0,
+        (void(binder(get_binder<TupleOut, Indexes>(binds))), 0)...
+      };
       return binder;
     }
   }
   
   template <typename... Ts>
   tuple_binder<std::tuple<Ts...>> make_tuple_binder(binder<Ts> &&... binds) {
-    return detail::make_tuple_binder<std::tuple<Ts...>>(std::make_tuple(binds...), std::make_index_sequence<sizeof...(Ts)>());
+    return detail::make_tuple_binder<std::tuple<Ts...>>
+    (std::make_tuple(binds...),
+     std::make_index_sequence<sizeof...(Ts)>());
   }
 
   template <typename... Ts>
   tuple_binder<std::tuple<Ts...>> make_tuple_binder(binder_impl<Ts> &&... binds) {
-    return detail::make_tuple_binder<std::tuple<Ts...>>(std::make_tuple(binder<Ts>(binds)...), std::make_index_sequence<sizeof...(Ts)>());
+    return detail::make_tuple_binder<std::tuple<Ts...>>
+    (std::make_tuple(binder<Ts>(binds)...),
+     std::make_index_sequence<sizeof...(Ts)>());
   }
   
   template <typename... Ts>
   tuple_binder<std::tuple<Ts...>> make_default_tuple_binder() {
-    return detail::make_tuple_binder<std::tuple<Ts...>>(std::make_tuple(binder<Ts>(value_binder<Ts>())...), std::make_index_sequence<sizeof...(Ts)>());
+    return detail::make_tuple_binder<std::tuple<Ts...>>
+    (std::make_tuple(binder<Ts>(value_binder<Ts>())...),
+     std::make_index_sequence<sizeof...(Ts)>());
   }
   
   template <typename T, std::size_t N>
   tuple_binder<std::array<T, N>> make_array_binder(binder<T> && binds) {
-    return detail::make_array_binder<std::array<T, N>>(binds, std::make_index_sequence<N>());
+    return detail::make_array_binder<std::array<T, N>>
+    (binds, std::make_index_sequence<N>());
   }
   
   template <typename T, std::size_t N>
-  tuple_binder<std::array<T, N>> make_array_binder(binder_impl<T> const & binds = value_binder<T>()) {
-    return detail::make_array_binder<std::array<T, N>>(binder<T>(binds), std::make_index_sequence<N>());
+  tuple_binder<std::array<T, N>> make_array_binder(binder_impl<T> const & binds
+                                                   = value_binder<T>()) {
+    return detail::make_array_binder<std::array<T, N>>
+    (binder<T>(binds), std::make_index_sequence<N>());
   }
 #else
   namespace detail {
@@ -88,16 +114,22 @@ namespace json { namespace binder {
     template <>
     struct tuple_binder_helper<0> {
       template <typename BindOut>
-      tuple_binder<BindOut> & operator()(tuple_binder<BindOut> & bind) const { return bind; }
+      tuple_binder<BindOut> & operator()(tuple_binder<BindOut> & bind) const {
+        return bind;
+      }
     };
     
     template <std::size_t N>
     struct tuple_binder_helper {
       template <typename BindOut, typename BindIn, typename... Ts>
-      tuple_binder<BindOut> & operator()(tuple_binder<BindOut> & bind, BindIn && ith, Ts &&... binders) const {
-        using get_binder_t = get_binder<BindOut, std::tuple_size<BindOut>::value-N>;
-        return tuple_binder_helper<N-1>()(bind(get_binder_t(std::forward<BindIn>(ith))),
-                                          std::forward<Ts>(binders)...);
+      tuple_binder<BindOut> & operator()(tuple_binder<BindOut> & bind,
+                                         BindIn && ith,
+                                         Ts &&... binders) const {
+        constexpr auto get_index = std::tuple_size<BindOut>::value-N;
+        using get_binder_t = get_binder<BindOut, get_index>;
+        return tuple_binder_helper<N-1>()
+        (bind(get_binder_t(std::forward<BindIn>(ith))),
+         std::forward<Ts>(binders)...);
       }
     };
     
@@ -106,14 +138,19 @@ namespace json { namespace binder {
     template <>
     struct array_binder_helper<1> {
       template <typename BindOut, typename BindIn>
-      tuple_binder<BindOut> & operator()(tuple_binder<BindOut> & bind, BindIn &&) const { return bind; }
+      tuple_binder<BindOut> & operator()(tuple_binder<BindOut> & bind,
+                                         BindIn &&) const {
+        return bind;
+      }
     };
     
     template <std::size_t N>
     struct array_binder_helper {
       template <typename BindOut, typename BindIn>
-      tuple_binder<BindOut> & operator()(tuple_binder<BindOut> & bind, BindIn && ith) const {
-        return array_binder_helper<N-1>()(bind(get_binder<BindOut, N-1>(ith)), std::forward<BindIn>(ith));
+      tuple_binder<BindOut> & operator()(tuple_binder<BindOut> & bind,
+                                         BindIn && ith) const {
+        return array_binder_helper<N-1>()(bind(get_binder<BindOut, N-1>(ith)),
+                                          std::forward<BindIn>(ith));
       }
     };
   }
@@ -121,19 +158,22 @@ namespace json { namespace binder {
   template <typename... Ts>
   tuple_binder<std::tuple<Ts...>> make_tuple_binder(binder<Ts> &&... binds) {
     tuple_binder<std::tuple<Ts...>> binder;
-    return detail::tuple_binder_helper<sizeof...(Ts)>()(binder, std::forward<Ts>(binds)...);
+    return detail::tuple_binder_helper<sizeof...(Ts)>()
+    (binder, std::forward<Ts>(binds)...);
   }
   
   template <typename... Ts>
   tuple_binder<std::tuple<Ts...>> make_tuple_binder(binder_impl<Ts> &&... binds) {
     tuple_binder<std::tuple<Ts...>> binder;
-    return detail::tuple_binder_helper<sizeof...(Ts)>()(binder, json::binder::binder<Ts>(binds)...);
+    return detail::tuple_binder_helper<sizeof...(Ts)>()
+    (binder, json::binder::binder<Ts>(binds)...);
   }
   
   template <typename... Ts>
   tuple_binder<std::tuple<Ts...>> make_default_tuple_binder() {
     tuple_binder<std::tuple<Ts...>> binder;
-    return detail::tuple_binder_helper<sizeof...(Ts)>()(binder, json::binder::binder<Ts>(value_binder<Ts>())...);
+    return detail::tuple_binder_helper<sizeof...(Ts)>()
+    (binder, json::binder::binder<Ts>(value_binder<Ts>())...);
   }
   
   template <typename T, std::size_t N>
@@ -143,9 +183,11 @@ namespace json { namespace binder {
   }
   
   template <typename T, std::size_t N>
-  tuple_binder<std::array<T, N>> make_array_binder(binder_impl<T> const & binds = value_binder<T>()) {
+  tuple_binder<std::array<T, N>> make_array_binder(binder_impl<T> const & binds
+                                                   = value_binder<T>()) {
     tuple_binder<std::array<T, N>> binder;
-    return detail::array_binder_helper<N>(binder, json::binder::binder<T>(binds));
+    return detail::array_binder_helper<N>(binder,
+                                          json::binder::binder<T>(binds));
   }
 #endif
-} }
+} }

+ 14 - 6
include/json/binder/json_direct_map_binder.hpp

@@ -7,15 +7,23 @@
 
 #pragma once
 
+#include "json_binder.hpp"
+#include "json_direct_binder.hpp"
+
+#include <map>
+
 namespace json { namespace binder {
   template <typename T, typename V, typename C>
   class associative_binder : public binder_impl<T> {
   public:
     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 binder_impl<T>* clone() const override {
+      return new associative_binder(*this);
+    }
     
-    virtual void parse(T& val, char const*& data, parser::options opts) 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 object type");
@@ -28,20 +36,20 @@ namespace json { namespace binder {
       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");
+          throw malformed_object_association(*data);
         }
         impl.parse(to_make, ++data, opts);
         map.emplace(key, std::move(to_make));
         json::helper::advance_to_boundary('}', data);
       }
       if (*data) ++data;
-      else throw json::unterminated_json_exception("Reached end of parse string without finding object end");
+      else throw unterminated_json_array();
     }
     
     virtual void write(T const& val, std::ostream & data) const override {
       data << '{';
       std::map<std::string, V> const & map = val.*ptr;
-      typename std::map<std::string, V>::const_iterator it = map.begin(), end = map.end();
+      auto it = map.begin(), end = map.end();
       if (it != end) {
         data << '"' << it->first << '"' << ':';
         impl.write(it->second, data);
@@ -63,7 +71,7 @@ namespace json { namespace binder {
   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; \
+    using associative_binder<T,V,C<std::string,V,O...>>::associative_binder; \
   }
 
   ASSOCIATIVE_DIRECT_BINDER( std::map );

+ 31 - 10
include/json/binder/json_direct_scalar_binder.hpp

@@ -7,14 +7,22 @@
 
 #pragma once
 
+#include "json_binder.hpp"
+#include "json_direct_binder.hpp"
+
+#include <string>
+
 namespace json { namespace binder {
   template <typename T>
   class direct_binder<T, bool> : public binder_impl<T> {
   public:
     explicit direct_binder(bool T::*p) : ptr(p) {}
-    virtual binder_impl<T>* clone() const override { return new direct_binder(*this); }
+    virtual binder_impl<T>* clone() const override {
+      return new direct_binder(*this);
+    }
     
-    virtual void parse(T& val, char const*& data, parser::options) const override {
+    virtual void parse(T& val, char const*& data,
+                       parser::options) const override {
       if (!strncmp(data, "true", 4)) {
         val.*ptr = true;
         data += 4;
@@ -34,13 +42,18 @@ namespace json { namespace binder {
     bool T::*ptr;
   };
   
+#define JSON_IS_INTEGRAL_T(Type) \
+  typename std::enable_if<std::is_integral<Type>::value>::type
   template <typename T, typename N>
-  class direct_binder<T, N, typename std::enable_if<std::is_integral<N>::value>::type> : public binder_impl<T> {
+  class direct_binder<T, N, JSON_IS_INTEGRAL_T(N)> : public binder_impl<T> {
   public:
     explicit direct_binder(N T::*p) : ptr(p) {}
-    virtual binder_impl<T>* clone() const override { return new direct_binder(*this); }
+    virtual binder_impl<T>* clone() const override {
+      return new direct_binder(*this);
+    }
     
-    virtual void parse(T& val, char const*& data, parser::options) const override {
+    virtual void parse(T& val, char const*& data,
+                       parser::options) const override {
       json::helper::parse_numeric(val.*ptr, data);
     }
     
@@ -51,13 +64,18 @@ namespace json { namespace binder {
     N T::*ptr;
   };
   
+#define JSON_IS_FLOATING_T(Type) \
+  typename std::enable_if<std::is_floating_point<Type>::value>::type
   template <typename T, typename N>
-  class direct_binder<T, N, typename std::enable_if<std::is_floating_point<N>::value>::type> : public binder_impl<T> {
+  class direct_binder<T, N, JSON_IS_FLOATING_T(N)> : public binder_impl<T> {
   public:
     explicit direct_binder(N T::*p) : ptr(p) {}
-    virtual binder_impl<T>* clone() const override { return new direct_binder(*this); }
+    virtual binder_impl<T>* clone() const override {
+      return new direct_binder(*this);
+    }
     
-    virtual void parse(T& val, char const*& data, parser::options) const override {
+    virtual void parse(T& val, char const*& data,
+                       parser::options) const override {
       json::helper::parse_double(val.*ptr, data);
     }
     
@@ -72,9 +90,12 @@ namespace json { namespace binder {
   class direct_binder<T, std::string> : public binder_impl<T> {
   public:
     explicit direct_binder(std::string T::*p) : ptr(p) {}
-    virtual binder_impl<T>* clone() const override { return new direct_binder(*this); }
+    virtual binder_impl<T>* clone() const override {
+      return new direct_binder(*this);
+    }
     
-    virtual void parse(T& val, char const*& data, parser::options) const override {
+    virtual void parse(T& val, char const*& data,
+                       parser::options) const override {
       json::helper::parse_string(val.*ptr, data);
     }
     

+ 14 - 6
include/json/binder/json_direct_vector_binder.hpp

@@ -7,19 +7,27 @@
 
 #pragma once
 
+#include "json_binder.hpp"
+#include "json_direct_binder.hpp"
+
+#include <vector>
+#include <list>
+#include <set>
+
 namespace json { namespace binder {
   template <typename T, typename V, typename C>
   class non_associative_binder : public binder_impl<T> {
   public:
     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 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 {
+    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");
-      }
+      if (ch != '[') { throw not_an_array_exception(val); }
       ++data;
       
       V to_make;
@@ -30,7 +38,7 @@ namespace json { namespace binder {
         json::helper::advance_to_boundary(']', data);
       }
       if (*data) ++data;
-      else throw json::unterminated_json_exception("Reached end of parse string without finding array end");
+      else throw unterminated_json_array();
     }
     
     virtual void write(T const& val, std::ostream & data) const override {

+ 24 - 12
include/json/binder/json_object_binder.hpp

@@ -7,18 +7,27 @@
 
 #pragma once
 
+#include "json_binder.hpp"
 #include "json_binder_discard.hpp"
+#include "json_direct_binder.hpp"
+
+#include <map>
+#include <set>
+#include <string>
 
 namespace json { namespace binder {
   template <typename T>
   class object_binder : public binder_impl<T> {
   public:
     object_binder() {}
-    virtual binder_impl<T>* clone() const override { return new object_binder(*this); }
+    virtual binder_impl<T>* clone() const override {
+      return new object_binder(*this);
+    }
     
     template <typename V>
-    object_binder& operator()(std::string const&k, V T::*ptr, binder_impl<V> const&v) {
-      return (*this)(k, binder<T>(new forward_binder<T, V>(ptr, binder<V>(v) )));
+    object_binder& operator()(std::string const&k, V T::*ptr,
+                              binder_impl<V> const&v) {
+      return (*this)(k, binder<T>(new forward_binder<T, V>(ptr, binder<V>(v))));
     }
 
     object_binder& operator()(std::string const&k, binder<T> const&v) {
@@ -26,19 +35,19 @@ namespace json { namespace binder {
       return *this;
     }
     
-    virtual void parse(T& object, char const*& data, parser::options opts) const override {
+    virtual void parse(T& 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(T).name());
+        throw not_an_object_exception(object);
       }
     }
     
     virtual void write(T const& val, std::ostream & data) const override {
       data << '{';
-      typename std::map<std::string, binder<T>>::const_iterator it = mapping.begin(),
-      end = mapping.end();
+      auto it = mapping.begin(), end = mapping.end();
       if (it != end) {
         data << '"' << it->first << '"' << ':';
         it->second.write(val, data);
@@ -51,17 +60,18 @@ namespace json { namespace binder {
       data << '}';
     }
     
-    void parse_object(T& object, char const*& data, parser::options opts) const {
+    void parse_object(T& object, char const*& data,
+                      parser::options opts) const {
       std::string key;
       std::set<std::string> unparsed_keys;
       for ( auto & p : mapping ) { unparsed_keys.insert(p.first); }
       while (*data && *data != '}') {
         if (json::helper::get_next_element(data) != '"') {
-          throw json::malformed_json_exception(std::string("Expected object key starting with '\"', got '") + *data + "' instead");
+          throw malformed_object_key(*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");
+          throw malformed_object_association(*data);
         }
         auto it = mapping.find(key);
         if (it != mapping.end()) {
@@ -75,9 +85,11 @@ namespace json { namespace binder {
         json::helper::advance_to_boundary('}', data);
       }
       if (*data) ++data;
-      else throw json::unterminated_json_exception("Reached end of parse string without finding object end");
+      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");
+        throw json::malformed_json_exception{
+          "missing certain keys from object construction TODO"
+        };
       }
     }
     

+ 5 - 1
include/json/binder/json_pointer_binder.hpp

@@ -8,10 +8,14 @@
 
 #pragma once
 
+#include "json_binder.hpp"
+
 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;
+    // Maybe a reference type
+    using Tr = decltype(*std::declval<P>());
+    using T = typename std::remove_reference<Tr>::type;
   public:
     pointer_binder(binder<T> impl) : m_impl(impl) {}
     

+ 57 - 21
include/json/binder/json_polymorphic_binder.hpp

@@ -8,20 +8,33 @@
 
 #pragma once
 
+#include "json_binder.hpp"
+
+#include <functional>
+#include <map>
+#include <string>
+
 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 binder_impl<PtrBase>* clone() const {
+      return new polymorphic_pointer_binder(*this);
+    }
     
-    virtual void parse(PtrBase & v, char const*& data, parser::options opts) const {
+    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);
+      else {
+        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(reinterpret_cast<Derived const &>(*v), os);
+      if (v) m_impl.write(dynamic_cast<Derived const &>(*v), os);
       else os << "null";
     }
   private:
@@ -31,25 +44,34 @@ namespace json { namespace binder {
   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");
+    // Maybe a reference type
+    using BaseR = decltype(*std::declval<Ptr>());
+    using Base = typename std::remove_reference<BaseR>::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); }
+    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) {
+    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); });
+      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 {
+    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());
+        throw not_an_object_exception(typeid(Base).name());
       }
     }
     virtual void write(Ptr const & val, std::ostream & data) const override {
@@ -57,7 +79,9 @@ namespace json { namespace binder {
       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); });
+                             [p](pair_t const & pair) {
+                               return pair.second(p);
+                             });
       if (it == reverse_lookup.end()) {
         throw std::domain_error("Unknown JSON binding object");
       }
@@ -67,35 +91,47 @@ namespace json { namespace binder {
       data << '}';
     }
     
-    void parse_object(Ptr & object, char const*& data, parser::options opts) const {
+    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");
+        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 + "'");
+        throw json::malformed_json_exception{
+          "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 + "'");
+      if (!*data) throw unterminated_json_object();
+      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 {
+    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");
+        throw malformed_object_key(*data);
       }
       json::helper::parse_string(key, data);
       if (key != expected) {
-        throw json::malformed_json_exception(std::string("Expecting '") + expected + "' polymorphic token, got '" + key + "' instead");
+        throw json::malformed_json_exception{
+          "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");
+        throw malformed_object_association(*data);
       }
     }
   private:

+ 20 - 7
include/json/binder/json_tuple_binder.hpp

@@ -7,23 +7,31 @@
 
 #pragma once
 
+#include "json_binder.hpp"
+#include "json_direct_binder.hpp"
+
+#include <vector>
+
 namespace json { namespace binder {
   template <typename T>
   class tuple_binder : public binder_impl<T> {
   public:
-    virtual binder_impl<T>* clone() const override { return new tuple_binder(*this); }
+    virtual binder_impl<T>* clone() const override {
+      return new tuple_binder(*this);
+    }
     
     tuple_binder& operator()(binder<T> const&b) {
       members.push_back(b);
       return *this;
     }
     
-    virtual void parse(T& object, char const*& data, parser::options opts) const override {
+    virtual void parse(T& object, char const*& data,
+                       parser::options opts) const override {
       const char ch = json::helper::get_next_element(data);
       if (ch == '[') {
         parse_tuple(object, ++data, opts);
       } else {
-        throw json::malformed_json_exception(std::string("Expected an array type for binding to ") + typeid(T).name());
+        throw not_an_array_exception(object);
       }
     }
     
@@ -41,7 +49,8 @@ namespace json { namespace binder {
       data << ']';
     }
     
-    void parse_tuple(T& object, char const*& data, parser::options opts) const {
+    void parse_tuple(T& object, char const*& data,
+                     parser::options opts) const {
       auto it = members.begin();
       while (*data && *data != ']' && it != members.end()) {
         it->parse(object, data, opts);
@@ -49,11 +58,15 @@ namespace json { namespace binder {
         ++it;
       }
       if (it != members.end()) {
-        throw json::malformed_json_exception("Failed to parse every member of tuple");
+        throw json::malformed_json_exception{
+          "Failed to parse every member of tuple"
+        };
       }
-      if (*data != ']') throw json::malformed_json_exception("Parsed every tuple element, but did not reach end");
+      if (*data != ']') throw json::malformed_json_exception{
+        "Parsed every tuple element, but did not reach end"
+      };
       else if (*data) ++data;
-      else throw json::unterminated_json_exception("Reached end of parse string without finding array end");
+      else throw unterminated_json_array();
     }
     
     template <typename E>

+ 2 - 89
include/json/json_binder.hpp

@@ -10,95 +10,8 @@
 #define json_binder_h
 #pragma once
 
-#include "json_common.hpp"
-
-#include <map>
-#include <string>
-#include <sstream>
-#include <vector>
-#include <list>
-
-namespace json {
-  namespace binder {
-    template <typename T>
-    class binder_impl {
-    public:
-      virtual binder_impl<T>* clone() const = 0;
-      virtual ~binder_impl() {}
-      virtual void parse(T&, char const*&, parser::options) const = 0;
-      virtual void write(T const&, std::ostream &) const = 0;
-    };
-
-    template <typename T>
-    class binder {
-    public:
-      binder() : impl(nullptr) {}
-      binder(binder const& other) : impl(other.impl->clone()) {}
-      binder(binder_impl<T> const* p) : impl(p) {}
-      binder(binder_impl<T> const& r) : impl(r.clone()) {}
-      
-      ~binder() { delete impl; }
-      
-      void parse(T& object, char const*& data, parser::options opts) const {
-        if (!impl) return; // error?
-        impl->parse(object, data, opts);
-      }
-      
-      void write(T const& object, std::ostream & data) const {
-        if (!impl) return; // error?
-        impl->write(object, data);
-      }
-    private:
-      binder_impl<T> const* impl;
-    };
-    
-    template <typename T, typename S = T>
-    class visitor {
-    public:
-      visitor(S& o, binder_impl<T>& b) : obj(o), b(b) {}
-      
-      void parse(char const* data, parser::options opts) {
-        b.parse(obj, data, opts);
-        if ( json::helper::get_next_element(data) && opts & parser::disable_concatenated_json_bodies ) {
-          throw malformed_json_exception("Config set to require json input be terminated");
-        }
-      }
-      
-      void write(std::ostream & data) const {
-        b.write(obj, data);
-      }
-      
-    private:
-      S& obj;
-      binder_impl<T>& b;
-    };
-    
-    template <typename T, typename S>
-    visitor<T, S> bind(S& object, binder_impl<T>& b) {
-      return {object, b};
-    }
-  }
-  
-  namespace parser {    
-    template <typename T>
-    void parse(binder::visitor<T>& visitor, char const* data, options opts = allow_all) {
-      visitor.parse(data, opts);
-    }
-        
-    template <typename T, typename S>
-    void write(binder::visitor<T, S> const & visitor, std::ostream & out) {
-      visitor.write(out);
-    }
-    
-    template <typename T, typename S>
-    void write(binder::visitor<T, S> const & visitor, std::string & data) {
-      std::stringstream ss;
-      visitor.write(ss);
-      data = ss.str();
-    }
-  }
-}
-
+#include "binder/json_binder.hpp"
+#include "binder/json_binder_visitor.hpp"
 #include "binder/json_binder_parser.hpp"
 #include "binder/json_direct_binder.hpp"
 #include "binder/json_tuple_binder.hpp"

+ 27 - 32
include/json/json_common.hpp

@@ -15,6 +15,8 @@
 #include <strings.h>
 #include <errno.h>
 
+#include "json_exception.h"
+
 namespace json {
   using string_jt = std::string;
   using double_jt = double;
@@ -24,26 +26,6 @@ namespace json {
   
   class value;
   
-  class malformed_json_exception :
-  public std::domain_error {
-    using std::domain_error::domain_error;
-  };
-  
-  class unterminated_json_exception :
-  public malformed_json_exception {
-    using malformed_json_exception::malformed_json_exception;
-  };
-  
-  class json_numeric_exception :
-  public std::domain_error {
-    using std::domain_error::domain_error;
-  };
-
-  class json_numeric_width_exception :
-  public json_numeric_exception {
-    using json_numeric_exception::json_numeric_exception;
-  };
-  
   template <typename T = json::int_jt>
   struct numeric_limits {
     static constexpr const T max{std::numeric_limits<T>::max()};
@@ -52,12 +34,12 @@ namespace json {
   };
   
   namespace {
-    const constexpr json::int_jt INT_JT_MAX = std::numeric_limits<json::int_jt>::max();
-    const constexpr json::int_jt INT_JT_MAX_LAST_DIGIT = (INT_JT_MAX % 10);
-    const constexpr json::int_jt INT_JT_MIN = std::numeric_limits<json::int_jt>::min();
-    const constexpr json::uint_jt INT_JT_OVER = json::uint_jt(INT_JT_MAX) + 1;
-    const constexpr json::uint_jt UINT_JT_MAX = json::uint_jt(0) - 1;
-    //  const constexpr json::uint_jt UINT_JT_MIN = 0;
+    const constexpr int_jt INT_JT_MAX = std::numeric_limits<int_jt>::max();
+    const constexpr int_jt INT_JT_MAX_LAST_DIGIT = (INT_JT_MAX % 10);
+    const constexpr int_jt INT_JT_MIN = std::numeric_limits<int_jt>::min();
+    const constexpr uint_jt INT_JT_OVER = uint_jt(INT_JT_MAX) + 1;
+    const constexpr uint_jt UINT_JT_MAX = uint_jt(0) - 1;
+    //  const constexpr uint_jt UINT_JT_MIN = 0;
   }
 }
 
@@ -102,7 +84,8 @@ namespace json { namespace helper {
    * @throws json::malformed_json_exception
    */
   std::string parse_string(char const * & data);
-  std::string replace_all(std::string str, std::string const & from, std::string const & to);
+  std::string replace_all(std::string str, std::string const & from,
+                          std::string const & to);
   
   template <typename T>
   void parse_string(T& json, char const * & data) {
@@ -115,11 +98,13 @@ namespace json { namespace helper {
     return std::strtod(begin, const_cast<char**>(&end));
   }
   template <>
-  inline float parse_double_impl<float>(char const * begin, char const * & end) {
+  inline float parse_double_impl<float>(char const * begin,
+                                        char const * & end) {
     return std::strtof(begin, const_cast<char**>(&end));
   }
   template <>
-  inline long double parse_double_impl<long double>(char const * begin, char const * & end) {
+  inline long double parse_double_impl<long double>(char const * begin,
+                                                    char const * & end) {
     return std::strtold(begin, const_cast<char**>(&end));
   }
   
@@ -130,7 +115,9 @@ namespace json { namespace helper {
     errno = 0;
     T tmp = parse_double_impl<T>(begin, data);
     if (errno != 0) {
-      throw json::json_numeric_width_exception("Number is out-of-range for floating-point type");
+      throw json::json_numeric_width_exception{
+        "Number is out-of-range for floating-point type"
+      };
     } else if ( begin == data ) {
       throw json::json_numeric_exception("Expected numeric data");
     }
@@ -138,6 +125,12 @@ namespace json { namespace helper {
     json = std::move(tmp);
   }
   
+  template <typename J>
+  bool has_too_many_digits(numeric_token_info const & info) {
+    return fls(static_cast<int_jt>(info.val))
+    > std::numeric_limits<J>::digits + info.is_negative;
+  }
+  
   template <typename J>
   void parse_numeric(J & json, char const * & data) {
     json::helper::get_next_element(data);
@@ -147,9 +140,11 @@ namespace json { namespace helper {
     } else if ( info.is_double || info.parse_numeric() == DOUBLE ) {
       throw json_numeric_exception("Expected integer, got double");
     } else if (info.base == numeric_token_info::decimal &&
-               (fls(static_cast<int_jt>(info.val)) > std::numeric_limits<J>::digits + info.is_negative
+               (has_too_many_digits<J>(info)
                || info.val > json::numeric_limits<J>::over)) {
-      throw json_numeric_width_exception("Integer width too small for parsed value");
+      throw json_numeric_width_exception{
+        "Integer width too small for parsed value"
+      };
     } else if (info.is_negative) {
       if (info.val == json::numeric_limits<J>::over) {
         json = json::numeric_limits<J>::min;

+ 72 - 0
include/json/json_exception.h

@@ -0,0 +1,72 @@
+//
+//  json_exception.h
+//  json
+//
+//  Created by Sam Jaffe on 11/17/18.
+//  Copyright © 2018 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <stdexcept>
+#include <typeinfo>
+
+namespace json {
+  class malformed_json_exception :
+  public std::domain_error {
+    using std::domain_error::domain_error;
+  };
+  
+  struct not_an_object_exception :
+  public malformed_json_exception {
+    template <typename T>
+    not_an_object_exception(T const &)
+    : not_an_object_exception(typeid(T).name()) {}
+    not_an_object_exception(char const * tname);
+  };
+
+  class not_an_array_exception :
+  public malformed_json_exception {
+  public:
+    template <typename T>
+    not_an_array_exception(T const &)
+    : not_an_array_exception(typeid(T).name()) {}
+  private:
+    not_an_array_exception(char const * tname);
+  };
+
+  struct malformed_object_key :
+  public malformed_json_exception {
+    malformed_object_key(char instead);
+  };
+
+  struct malformed_object_association :
+  public malformed_json_exception {
+    malformed_object_association(char instead);
+  };
+
+  class unterminated_json_exception :
+  public malformed_json_exception {
+    using malformed_json_exception::malformed_json_exception;
+  };
+  
+  struct unterminated_json_array :
+  public unterminated_json_exception {
+    unterminated_json_array();
+  };
+
+  struct unterminated_json_object :
+  public unterminated_json_exception {
+    unterminated_json_object();
+  };
+
+  class json_numeric_exception :
+  public std::domain_error {
+    using std::domain_error::domain_error;
+  };
+  
+  class json_numeric_width_exception :
+  public json_numeric_exception {
+    using json_numeric_exception::json_numeric_exception;
+  };
+}

+ 6 - 0
json.xcodeproj/project.pbxproj

@@ -24,6 +24,8 @@
 		CD6DC460219FA2200052CD08 /* json_binder_terminate_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD2B09881E6374F300D6D23A /* json_binder_terminate_test.cxx */; };
 		CD6DC461219FA2230052CD08 /* json_binder_custom_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD84C4EB1F68908F002014D3 /* json_binder_custom_test.cxx */; };
 		CD6DC462219FA2420052CD08 /* json_binder_polymorphic_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD31E1E9219CE85A001C2AF1 /* json_binder_polymorphic_test.cxx */; };
+		CD6DC46421A083D40052CD08 /* json_exception.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD6DC46321A083D40052CD08 /* json_exception.cxx */; };
+		CD6DC46521A083D40052CD08 /* json_exception.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD6DC46321A083D40052CD08 /* json_exception.cxx */; };
 		CDB2F7431C5D48090067C2EC /* json.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CDB2F7411C5D48090067C2EC /* json.cpp */; };
 /* End PBXBuildFile section */
 
@@ -87,6 +89,7 @@
 		CD6DC437219F89E90052CD08 /* GoogleMock.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GoogleMock.xcodeproj; path = "../../../gmock-xcode-master/GoogleMock.xcodeproj"; sourceTree = "<group>"; };
 		CD6DC44A219F8A360052CD08 /* json-direct-test.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "json-direct-test.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD6DC44E219F8A360052CD08 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		CD6DC46321A083D40052CD08 /* json_exception.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = json_exception.cxx; sourceTree = "<group>"; };
 		CD84C4EB1F68908F002014D3 /* json_binder_custom_test.cxx */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.cpp; fileEncoding = 4; indentWidth = 2; path = json_binder_custom_test.cxx; sourceTree = "<group>"; tabWidth = 2; };
 		CDB2F7331C5D47F70067C2EC /* libjson.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libjson.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
 		CDB2F7411C5D48090067C2EC /* json.cpp */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.cpp.cpp; path = json.cpp; sourceTree = "<group>"; tabWidth = 2; };
@@ -126,6 +129,7 @@
 			isa = PBXGroup;
 			children = (
 				CD472C751CCC1ABD0084C8D6 /* json_common.cpp */,
+				CD6DC46321A083D40052CD08 /* json_exception.cxx */,
 				CDB2F7411C5D48090067C2EC /* json.cpp */,
 				CD472C7F1CCDA4B00084C8D6 /* json_parser.cpp */,
 				CD17473E1D4C1DFD000C344B /* json_binder_discard.cpp */,
@@ -364,6 +368,7 @@
 			files = (
 				CD3C80C61D6A711000ACC795 /* json_binder_discard.cpp in Sources */,
 				CD3C80C71D6A711000ACC795 /* json_common.cpp in Sources */,
+				CD6DC46521A083D40052CD08 /* json_exception.cxx in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -391,6 +396,7 @@
 				CD472C801CCDA4B00084C8D6 /* json_parser.cpp in Sources */,
 				CDB2F7431C5D48090067C2EC /* json.cpp in Sources */,
 				CD472C761CCC1ABD0084C8D6 /* json_common.cpp in Sources */,
+				CD6DC46421A083D40052CD08 /* json_exception.cxx in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 116 - 117
src/json_common.cpp

@@ -10,135 +10,134 @@
 #include <cstring>
 #include <map>
 
-namespace json {
-  namespace helper {
-    namespace {
-      std::string get_no_end_error(char expected, char found) {
-        char str[64] = { '\0' };
-        snprintf(str, sizeof(str),
-                 "Expected delimeter: ',' or '%c', got '%c' instead",
-                 expected, found);
-        return str;
-      }
+namespace json { namespace helper {
+  namespace {
+    std::string get_no_end_error(char expected, char found) {
+      char str[64] = { '\0' };
+      snprintf(str, sizeof(str),
+               "Expected delimeter: ',' or '%c', got '%c' instead",
+               expected, found);
+      return str;
     }
+  }
+  
+  namespace {
+    std::map<numeric_token_info::parse_state, uint_jt> bases{
+      { numeric_token_info::decimal,     10 },
+      { numeric_token_info::octal,        8 },
+      { numeric_token_info::hexadecimal, 16 }
+    };
+    std::map<numeric_token_info::parse_state, std::string> allowed{
+      { numeric_token_info::decimal,     "0123456789" },
+      { numeric_token_info::octal,       "01234567" },
+      { numeric_token_info::hexadecimal, "0123456789aAbBcCdDeEfF" }
+    };
+    std::map<char, uint_jt> values{
+      { '0',  0 }, { '1',  1 }, { '2',  2 }, { '3',  3 },
+      { '4',  4 }, { '5',  5 }, { '6',  6 }, { '7',  7 },
+      { '8',  8 }, { '9',  9 }, { 'a', 10 }, { 'A', 10 },
+      { 'b', 11 }, { 'B', 11 }, { 'c', 12 }, { 'C', 12 },
+      { 'd', 13 }, { 'D', 13 }, { 'e', 14 }, { 'E', 14 },
+      { 'f', 15 }, { 'F', 15 }
+    };
+    std::map<numeric_token_info::parse_state, uint_jt> thresholds{
+      { numeric_token_info::decimal,     UINT_JT_MAX / 10 },
+      { numeric_token_info::octal,       UINT_JT_MAX /  8 },
+      { numeric_token_info::hexadecimal, UINT_JT_MAX / 16 }
+    };
+    std::map<numeric_token_info::parse_state, uint_jt> last_digits{
+      { numeric_token_info::decimal,     INT_JT_MAX % 10 },
+      { numeric_token_info::octal,       INT_JT_MAX %  8 },
+      { numeric_token_info::hexadecimal, INT_JT_MAX % 16 }
+    };
     
-    namespace {
-      std::map<numeric_token_info::parse_state, uint_jt> bases{
-        { numeric_token_info::decimal,     10 },
-        { numeric_token_info::octal,        8 },
-        { numeric_token_info::hexadecimal, 16 }
-      };
-      std::map<numeric_token_info::parse_state, std::string> allowed{
-        { numeric_token_info::decimal,     "0123456789" },
-        { numeric_token_info::octal,       "01234567" },
-        { numeric_token_info::hexadecimal, "0123456789aAbBcCdDeEfF" }
-      };
-      std::map<char, uint_jt> values{
-        { '0',  0 }, { '1',  1 }, { '2',  2 }, { '3',  3 },
-        { '4',  4 }, { '5',  5 }, { '6',  6 }, { '7',  7 },
-        { '8',  8 }, { '9',  9 }, { 'a', 10 }, { 'A', 10 },
-        { 'b', 11 }, { 'B', 11 }, { 'c', 12 }, { 'C', 12 },
-        { 'd', 13 }, { 'D', 13 }, { 'e', 14 }, { 'E', 14 },
-        { 'f', 15 }, { 'F', 15 }
-      };
-      std::map<numeric_token_info::parse_state, uint_jt> thresholds{
-        { numeric_token_info::decimal,     UINT_JT_MAX / 10 },
-        { numeric_token_info::octal,       UINT_JT_MAX /  8 },
-        { numeric_token_info::hexadecimal, UINT_JT_MAX / 16 }
-      };
-      std::map<numeric_token_info::parse_state, uint_jt> last_digits{
-        { numeric_token_info::decimal,     INT_JT_MAX % 10 },
-        { numeric_token_info::octal,       INT_JT_MAX %  8 },
-        { numeric_token_info::hexadecimal, INT_JT_MAX % 16 }
-      };
-
-    }
-
-    // TODO - parse hex and octal
-    numeric_token_info::numeric_token_info(char const * start)
-    : val(0)
-    , base(decimal)
-    , it(start)
-    , end(start)
-    , is_double(false)
-    , is_negative(*start == '-') {
-      if ( is_negative ) { ++it; ++end; }
-      if ( *end == '0' ) {
-        base = octal;
+  }
+  
+  // TODO - parse hex and octal
+  numeric_token_info::numeric_token_info(char const * start)
+  : val(0)
+  , base(decimal)
+  , it(start)
+  , end(start)
+  , is_double(false)
+  , is_negative(*start == '-') {
+    if ( is_negative ) { ++it; ++end; }
+    if ( *end == '0' ) {
+      base = octal;
+      ++end;
+      if (strchr("xX", *end)) {
+        base = hexadecimal;
         ++end;
-        if (strchr("xX", *end)) {
-          base = hexadecimal;
-          ++end;
-        }
-      }
-      for (char c = *end;
-           !strchr(",]}", c) && !isspace(c);
-           c = *++end) {
-        is_double |= !strchr(allowed[base].c_str(), c);
-      }
-      
-      if ( is_negative && base != decimal ) {
-        throw json_numeric_exception("Only decimal numbers can be recorded as negative");
-      }
-      if (end == it) {
-        throw unterminated_json_exception("Expected any token, got nothing");
       }
     }
+    for (char c = *end;
+         !strchr(",]}", c) && !isspace(c);
+         c = *++end) {
+      is_double |= !strchr(allowed[base].c_str(), c);
+    }
     
-    numeric_state numeric_token_info::parse_numeric() {
-      uint_jt const threshold = thresholds[base];
-      uint_jt const last_digit = last_digits[base];
-      val = 0;
-      for (char c = *it; it != end; c = *++it) {
-        uint_jt digit = values[c];
-        if (val > threshold ||
-            ( val == threshold && ((it + 1) < end ||
-              digit > last_digit))) {
-          return DOUBLE;
-        }
-        val = (bases[base] * val) + digit;
-      }
-      return INTEGER;
+    if ( is_negative && base != decimal ) {
+      throw json_numeric_exception("Only decimal numbers can be recorded as negative");
     }
-
-
-    const char get_next_element(char const*& data) {
-      while (isspace(*data)) ++data;
-      return *data;
+    if (end == it) {
+      throw unterminated_json_exception("Expected any token, got nothing");
     }
-    
-    void advance_to_boundary(char const endtok, char const*& data) {
-      char const next = get_next_element(data);
-      if (next == ',') {
-        ++data;
-      } else if (next != endtok) {
-        throw json::unterminated_json_exception(get_no_end_error(endtok, *data));
+  }
+  
+  numeric_state numeric_token_info::parse_numeric() {
+    uint_jt const threshold = thresholds[base];
+    uint_jt const last_digit = last_digits[base];
+    val = 0;
+    for (char c = *it; it != end; c = *++it) {
+      uint_jt digit = values[c];
+      if (val > threshold ||
+          ( val == threshold && ((it + 1) < end ||
+                                 digit > last_digit))) {
+        return DOUBLE;
       }
+      val = (bases[base] * val) + digit;
     }
-    
-    int reverse_count(char const * data, char val) {
-      int i = 0;
-      while (*data-- == val) { ++i; }
-      return i;
+    return INTEGER;
+  }
+  
+  
+  const char get_next_element(char const*& data) {
+    while (isspace(*data)) ++data;
+    return *data;
+  }
+  
+  void advance_to_boundary(char const endtok, char const*& data) {
+    char const next = get_next_element(data);
+    if (next == ',') {
+      ++data;
+    } else if (next != endtok) {
+      throw unterminated_json_exception(get_no_end_error(endtok, *data));
     }
-    
-    std::string replace_all(std::string str, std::string const & from, std::string const & to) {
-      std::string::size_type start_pos = 0;
-      while((start_pos = str.find(from, start_pos)) != std::string::npos) {
-        str.replace(start_pos, from.length(), to);
-        start_pos += to.length(); // ...
-      }
-      return str;
+  }
+  
+  int reverse_count(char const * data, char val) {
+    int i = 0;
+    while (*data-- == val) { ++i; }
+    return i;
+  }
+  
+  std::string replace_all(std::string str, std::string const & from,
+                          std::string const & to) {
+    std::string::size_type start_pos = 0;
+    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
+      str.replace(start_pos, from.length(), to);
+      start_pos += to.length(); // ...
     }
-    
-    std::string parse_string(char const * & data) {
-      char const* start = data;
-      while (*++data) {
-        if (*data == '"' && (reverse_count(data-1, '\\') % 2) == 0) {
-          return replace_all(std::string(start+1, data++), "\\\"", "\"");
-        }
+    return str;
+  }
+  
+  std::string parse_string(char const * & data) {
+    char const* start = data;
+    while (*++data) {
+      if (*data == '"' && (reverse_count(data-1, '\\') % 2) == 0) {
+        return replace_all(std::string(start+1, data++), "\\\"", "\"");
       }
-      throw json::unterminated_json_exception("Could not locate end of string");
     }
+    throw unterminated_json_exception("Could not locate end of string");
   }
-}
+} }

+ 56 - 0
src/json_exception.cxx

@@ -0,0 +1,56 @@
+//
+//  json_exception.cxx
+//  json
+//
+//  Created by Sam Jaffe on 11/17/18.
+//  Copyright © 2018 Sam Jaffe. All rights reserved.
+//
+
+#include "json/json_exception.h"
+
+#include <string>
+
+using namespace json;
+
+namespace {
+  const std::string uterm_array_msg{
+    "Reached end of parse string without finding array end"
+  };
+  const std::string uterm_object_msg{
+    "Reached end of parse string without finding object end"
+  };
+  
+  const std::string object_assoc_msg{
+    "Expected key:value pair delimited by ':', got '"
+  };
+  
+  const std::string object_key_msg{
+    "Expected object key starting with '\"', got '"
+  };
+  
+  const std::string not_object_msg{
+    "Expected an object type for binding to "
+  };
+
+  const std::string not_array_msg{
+    "Expected an array type for binding to "
+  };
+}
+
+not_an_object_exception::not_an_object_exception(char const * tname)
+: malformed_json_exception(not_object_msg + tname) {}
+
+not_an_array_exception::not_an_array_exception(char const * tname)
+: malformed_json_exception(not_array_msg + tname) {}
+
+malformed_object_key::malformed_object_key(char instead)
+: malformed_json_exception(object_key_msg + instead + "' instead") {}
+
+malformed_object_association::malformed_object_association(char instead)
+: malformed_json_exception(object_assoc_msg + instead + "' instead") {}
+
+unterminated_json_array::unterminated_json_array()
+: unterminated_json_exception(uterm_array_msg) {}
+
+unterminated_json_object::unterminated_json_object()
+: unterminated_json_exception(uterm_array_msg) {}

+ 26 - 10
test/json_binder_object_test.cxx

@@ -21,7 +21,9 @@ 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 {
@@ -36,7 +38,8 @@ protected:
 };
 
 TEST_F(JsonBinderObjectTest, ParsesSuccessfully) {
-  char data[] = "{ \"count\":10, \"average\":1.0, \"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }}";
+  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}}} };
   EXPECT_NO_THROW(parse(json::binder::bind(out, GetBinder()), data, allow_all));
@@ -44,7 +47,9 @@ TEST_F(JsonBinderObjectTest, ParsesSuccessfully) {
 }
 
 TEST_F(JsonBinderObjectTest, IgnoresUnknownKeysByDefault) {
-  char data[] = "{ \"count\":10, \"average\":1.0, \"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, \"lemon\":true}";
+  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}}} };
   EXPECT_NO_THROW(parse(json::binder::bind(out, GetBinder()), data, allow_all));
@@ -52,7 +57,9 @@ TEST_F(JsonBinderObjectTest, IgnoresUnknownKeysByDefault) {
 }
 
 TEST_F(JsonBinderObjectTest, ThrowsOnUnknownKeyWithSetting) {
-  char data[] = "{ \"count\":10, \"average\":1.0, \"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, \"lemon\":true}";
+  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),
@@ -76,7 +83,8 @@ TEST_F(JsonBinderObjectTest, ThrowsOnMissingExpectedKeyWithSetting) {
 }
 
 TEST_F(JsonBinderObjectTest, ThrowsOnMissingStartToken) {
-  char data[] = "\"count\":10, \"average\":1.0, \"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] } }";
+  char data[] = "\"count\":10, \"average\":1.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);
@@ -90,28 +98,35 @@ TEST_F(JsonBinderObjectTest, ThrowsIfMissingValueForKey) {
 }
 
 TEST_F(JsonBinderObjectTest, ThrowsOnMissingEndToken) {
-  char data[] = "{ \"count\":10, \"average\":1.0, \"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }";
+  char data[] = "{ \"count\":10, \"average\":1.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);
 }
 
 TEST_F(JsonBinderObjectTest, ThrowsOnMissingValueForKeyEvenForUnknownKey) {
-  char data[] = "{ \"count\":10, \"average\":1.0, \"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, \"lemon\":{\"key\": } }";
+  char data[] = "{ \"count\":10, \"average\":1.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 }";
+  char data[] = "{ \"count\":10, \"average\":1.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);
 }
 
 TEST_F(JsonBinderObjectTest, ThrowsOnMissingEndTokenEvenForUnknownKey2) {
-  char data[] = "{ \"count\":10, \"average\":1.0, \"data\":{ \"key1\":[1, 2], \"key2\":[3, 4] }, \"lemon\":{\"key\":false }";
+  char data[] = "{ \"count\":10, \"average\":1.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);
@@ -125,7 +140,8 @@ std::string ify(double value) {
 
 TEST_F(JsonBinderObjectTest, WritesBackOriginalObject) {
   std::string const dbl = ify(1.0);
-  std::string const expected = "{\"average\":"+dbl+",\"count\":10,\"data\":{\"key1\":[1,2],\"key2\":[3,4]}}";
+  std::string const expected = "{\"average\":1.000000,\"count\":10,"
+  "\"data\":{\"key1\":[1,2],\"key2\":[3,4]}}";
   std::stringstream ss;
   TestObject const in = { 10, 1.0, { { "key1", {1, 2} }, { "key2", {3, 4} } } };
   EXPECT_NO_THROW(write(json::binder::bind(in, GetBinder()), ss));

+ 9 - 4
test/json_binder_polymorphic_test.cxx

@@ -47,6 +47,11 @@ protected:
   }
 };
 
+template <typename T>
+bool instanceof(Base * ptr) {
+  return dynamic_cast<T*>(ptr) != nullptr;
+}
+
 using namespace ::testing;
 
 TEST_F(JsonBinderPolymorphicTest, CanHitLeftPolymorph) {
@@ -54,8 +59,8 @@ TEST_F(JsonBinderPolymorphicTest, CanHitLeftPolymorph) {
   pBase out;
   Left expected; expected.a = 1;
   EXPECT_NO_THROW(parse(json::binder::bind(out, GetBinder()), data));
-  EXPECT_THAT(&*out, Not(IsNull()));
-  EXPECT_THAT(dynamic_cast<Left*>(&*out), Not(IsNull()));
+  EXPECT_THAT(out, Not(IsNull()));
+  EXPECT_PRED1(instanceof<Left>, out.get());
   EXPECT_THAT(dynamic_cast<Left&>(*out), expected);
 }
 
@@ -64,7 +69,7 @@ TEST_F(JsonBinderPolymorphicTest, CanHitRightPolymorph) {
   pBase out;
   Right expected; expected.a = "hello";
   EXPECT_NO_THROW(parse(json::binder::bind(out, GetBinder()), data));
-  EXPECT_THAT(&*out, Not(IsNull()));
-  EXPECT_THAT(dynamic_cast<Right*>(&*out), Not(IsNull()));
+  EXPECT_THAT(out, Not(IsNull()));
+  EXPECT_PRED1(instanceof<Right>, out.get());
   EXPECT_THAT(dynamic_cast<Right&>(*out), expected);
 }