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