json_polymorphic_binder.hpp 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. //
  2. // json_polymorphic_binder.hpp
  3. // json
  4. //
  5. // Created by Sam Jaffe on 11/15/18.
  6. // Copyright © 2018 Sam Jaffe. All rights reserved.
  7. //
  8. #pragma once
  9. namespace json { namespace binder {
  10. template <typename PtrBase, typename Derived>
  11. class polymorphic_pointer_binder : public binder_impl<PtrBase> {
  12. public:
  13. polymorphic_pointer_binder(binder<Derived> impl) : m_impl(impl) {}
  14. virtual binder_impl<PtrBase>* clone() const { return new polymorphic_pointer_binder(*this); }
  15. virtual void parse(PtrBase & v, char const*& data, parser::options opts) const {
  16. if (!strncmp(data, "null", 4)) v = nullptr;
  17. else m_impl.parse(reinterpret_cast<Derived &>(*(v = PtrBase(new Derived))), data, opts);
  18. }
  19. void write(PtrBase const & v, std::ostream & os) const {
  20. if (v) m_impl.write(reinterpret_cast<Derived const &>(*v), os);
  21. else os << "null";
  22. }
  23. private:
  24. binder<Derived> m_impl;
  25. };
  26. template <typename Ptr>
  27. class polymorphic_binder : public binder_impl<Ptr> {
  28. private:
  29. using Base = typename std::remove_reference<decltype(*std::declval<Ptr>())>::type;
  30. static_assert(std::is_polymorphic<Base>::value, "Must use a polymorphic type");
  31. public:
  32. polymorphic_binder() = default;
  33. virtual binder_impl<Ptr>* clone() const override { return new polymorphic_binder(*this); }
  34. template <typename Derived>
  35. polymorphic_binder& operator()(std::string const&k, binder_impl<Derived> const&v) {
  36. mapping.emplace(k, polymorphic_pointer_binder<Ptr, Derived>(v));
  37. reverse_lookup.emplace(k, [](Base const * p) { return dynamic_cast<Derived const *>(p); });
  38. return *this;
  39. }
  40. virtual void parse(Ptr & object, char const*& data, parser::options opts) const override {
  41. const char ch = json::helper::get_next_element(data);
  42. if (ch == '{') {
  43. parse_object(object, ++data, opts);
  44. } else {
  45. throw json::malformed_json_exception(std::string("Expected an object type for binding to ") + typeid(Base).name());
  46. }
  47. }
  48. virtual void write(Ptr const & val, std::ostream & data) const override {
  49. data << '{';
  50. using pair_t = typename decltype(reverse_lookup)::value_type;
  51. Base const * p = std::addressof(*val);
  52. auto it = std::find_if(reverse_lookup.begin(), reverse_lookup.end(),
  53. [p](pair_t const & pair) { return pair.second(p); });
  54. if (it == reverse_lookup.end()) {
  55. throw std::domain_error("Unknown JSON binding object");
  56. }
  57. data << "\"@id\":\"" << it->first << "\",";
  58. data << "\"@value\":";
  59. mapping.find(it->first)->second.write(val, data);
  60. data << '}';
  61. }
  62. void parse_object(Ptr & object, char const*& data, parser::options opts) const {
  63. std::string key;
  64. fetch_expected_key("@id", data);
  65. if (json::helper::get_next_element(++data) != '"') {
  66. throw json::malformed_json_exception(std::string("Expected polymorphic id starting with '\"', got '") + *data + "' instead");
  67. }
  68. json::helper::parse_string(key, data);
  69. auto it = mapping.find(key);
  70. if (it == mapping.end()) {
  71. throw json::malformed_json_exception(std::string("Unknown polymorphic type-id: '") + key + "'");
  72. }
  73. fetch_expected_key("@value", ++data);
  74. it->second.parse(object, ++data, opts);
  75. if (!*data) throw json::unterminated_json_exception("Reached end of parse string without finding object end");
  76. else if (*data != '}') throw json::malformed_json_exception(std::string("Unexpected non-terminated object '") + *data + "'");
  77. else ++data;
  78. }
  79. void fetch_expected_key(std::string const & expected, char const*& data) const {
  80. std::string key;
  81. if (json::helper::get_next_element(data) != '"') {
  82. throw json::malformed_json_exception(std::string("Expected object key starting with '\"', got '") + *data + "' instead");
  83. }
  84. json::helper::parse_string(key, data);
  85. if (key != expected) {
  86. throw json::malformed_json_exception(std::string("Expecting '") + expected + "' polymorphic token, got '" + key + "' instead");
  87. }
  88. if (json::helper::get_next_element(data) != ':') {
  89. throw json::malformed_json_exception(std::string("Expected key:value pair delimited by ':', got '") + *data + "' instead");
  90. }
  91. }
  92. private:
  93. std::map<std::string, std::function<bool(Base const*)>> reverse_lookup;
  94. std::map<std::string, binder<Ptr>> mapping;
  95. };
  96. } }