// // json_polymorphic_binder.hpp // json // // Created by Sam Jaffe on 11/15/18. // Copyright © 2018 Sam Jaffe. All rights reserved. // #pragma once #include "json_binder.hpp" #include #include #include namespace json { namespace binder { template class polymorphic_pointer_binder : public binder_impl { public: polymorphic_pointer_binder(binder impl) : m_impl(impl) {} virtual binder_impl * clone() const { return new polymorphic_pointer_binder(*this); } virtual void parse(PtrBase & v, char const *& data, parser::options opts) const { v = PtrBase(new Derived); Derived & actual = dynamic_cast(*v); m_impl.parse(actual, data, opts); } void write(PtrBase const & v, std::ostream & os) const { m_impl.write(dynamic_cast(*v), os); } private: binder m_impl; }; template class polymorphic_binder : public binder_impl { private: // Maybe a reference type using BaseR = decltype(*std::declval()); using Base = typename std::remove_reference::type; static_assert(std::is_polymorphic::value, "Must use a polymorphic type"); public: polymorphic_binder() = default; virtual binder_impl * clone() const override { return new polymorphic_binder(*this); } template polymorphic_binder & operator()(std::string const & k, binder_impl const & v) { mapping.emplace(k, polymorphic_pointer_binder(v)); reverse_lookup.emplace( k, [](Base const * p) { return dynamic_cast(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 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 { if (!val) { data << "null"; } else { write_object(val, data); } } private: 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{"Unknown polymorphic type-id: '" + key + "'"}; } fetch_expected_key("@value", ++data); it->second.parse(object, ++data, opts); if (!*data) throw unterminated_json_object(); else if (*data != '}') throw json::malformed_json_exception{ std::string("Unexpected non-terminated object '") + *data + "'"}; else ++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; if (json::helper::get_next_element(data) != '"') { throw malformed_object_key(*data); } json::helper::parse_string(key, data); if (key != expected) { throw json::malformed_json_exception{"Expecting '" + expected + "' polymorphic token, got '" + key + "' instead"}; } if (json::helper::get_next_element(data) != ':') { throw malformed_object_association(*data); } } std::map> reverse_lookup; std::map> mapping; }; }}