JSON Voorhees
Killer JSON for C++
Loading...
Searching...
No Matches
polymorphic_adapter.hpp
Go to the documentation of this file.
1/// \file jsonv/serialization/polymorphic_adapter.hpp
2///
3/// Copyright (c) 2017-2020 by Travis Gockel. All rights reserved.
4///
5/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License
6/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later
7/// version.
8///
9/// \author Travis Gockel (travis@gockelhut.com)
10#pragma once
11
12#include <jsonv/config.hpp>
14
15namespace jsonv
16{
17
18/// \addtogroup Serialization
19/// \{
20
21/// What to do when serializing a keyed subtype of a \c polymorphic_adapter. See
22/// \c polymorphic_adapter::add_subtype_keyed.
23enum class keyed_subtype_action : unsigned char
24{
25 /// Don't do any checking or insertion of the expected key/value pair.
26 none,
27 /// Ensure the correct key/value pair was inserted by serialization. Throws \c std::runtime_error if it wasn't.
28 check,
29 /// Insert the correct key/value pair as part of serialization. Throws \c std::runtime_error if the key is already
30 /// present.
31 insert
32};
33
34/// An adapter which can create polymorphic types. This allows you to parse JSON directly into a type heirarchy without
35/// some middle layer.
36///
37/// @code
38/// [
39/// {
40/// "type": "worker",
41/// "name": "Adam"
42/// },
43/// {
44/// "type": "manager",
45/// "name": "Bob",
46/// "head": "Development"
47/// }
48/// ]
49/// @endcode
50///
51/// \tparam TPointer Some pointer-like type (likely \c unique_ptr or \c shared_ptr) you wish to extract values into. It
52/// must support \c operator*, an explicit conversion to \c bool, construction with a pointer to a
53/// subtype of what it contains and default construction.
54///
55template <typename TPointer>
57 public adapter_for<TPointer>
58{
59public:
60 using match_predicate = std::function<bool (const extraction_context&, const value&)>;
61
62public:
63 polymorphic_adapter() = default;
64
65 /// Add a subtype which can be transformed into \c TPointer which will be called if the discriminator \a pred is
66 /// matched.
67 ///
68 /// \see add_subtype_keyed
69 template <typename T>
70 void add_subtype(match_predicate pred)
71 {
72 _subtype_ctors.emplace_back(std::move(pred),
73 [] (const extraction_context& context, const value& value)
74 {
75 return TPointer(new T(context.extract<T>(value)));
76 }
77 );
78 }
79
80 /// Add a subtype which can be transformed into \c TPointer which will be called if given a JSON \c value with
81 /// \c kind::object which has a member with \a key and the provided \a expected_value.
82 ///
83 /// \see add_subtype
84 template <typename T>
85 void add_subtype_keyed(std::string key,
88 {
89 std::type_index tidx = std::type_index(typeid(T));
90 if (!_serialization_actions.emplace(tidx, std::make_tuple(key, expected_value, action)).second)
91 throw duplicate_type_error("polymorphic_adapter subtype", std::type_index(typeid(T)));
92
93 match_predicate op = [key, expected_value] (const extraction_context&, const value& value)
94 {
95 if (!value.is_object())
96 return false;
97 auto iter = value.find(key);
98 return iter != value.end_object()
99 && iter->second == expected_value;
100 };
101 return add_subtype<T>(op);
102 }
103
104 /// \{
105 /// When extracting a C++ value, should \c kind::null in JSON automatically become a default-constructed \c TPointer
106 /// (which is usually the \c null representation)?
108 {
109 _check_null_input = on;
110 }
111
112 bool check_null_input() const
113 {
114 return _check_null_input;
115 }
116 /// }
117
118 /// \{
119 /// When converting with \c to_json, should a \c null input translate into a \c kind::null?
121 {
122 _check_null_output = on;
123 }
124
125 bool check_null_output() const
126 {
127 return _check_null_output;
128 }
129 /// \}
130
131protected:
132 virtual TPointer create(const extraction_context& context, const value& from) const override
133 {
134 using std::begin;
135 using std::end;
136
137 if (_check_null_input && from.is_null())
138 return TPointer();
139
140 auto iter = std::find_if(begin(_subtype_ctors), end(_subtype_ctors),
141 [&] (const std::pair<match_predicate, create_function>& pair)
142 {
143 return pair.first(context, from);
144 }
145 );
146 if (iter != end(_subtype_ctors))
147 return iter->second(context, from);
148 else
149 throw extraction_error(context.path(),
150 std::string("No discriminators matched JSON value: ") + to_string(from)
151 );
152 }
153
154 virtual value to_json(const serialization_context& context, const TPointer& from) const override
155 {
156 if (_check_null_output && !from)
157 return null;
158
159 value serialized = context.to_json(typeid(*from), static_cast<const void*>(&*from));
160
161 auto action_iter = _serialization_actions.find(std::type_index(typeid(*from)));
162 if (action_iter != _serialization_actions.end())
163 {
164 auto errmsg = [&]()
165 {
166 return " polymorphic_adapter<" + demangle(typeid(TPointer).name()) + ">"
167 "subtype(" + demangle(typeid(*from).name()) + ")";
168 };
169
170 const std::string& key = std::get<0>(action_iter->second);
171 const value& val = std::get<1>(action_iter->second);
172 const keyed_subtype_action& action = std::get<2>(action_iter->second);
173
174 switch (action)
175 {
177 break;
179 if (!serialized.is_object())
180 throw std::runtime_error("Expected keyed subtype to serialize as an object." + errmsg());
181 if (!serialized.count(key))
182 throw std::runtime_error("Expected subtype key not found." + errmsg());
183 if (serialized.at(key) != val)
184 throw std::runtime_error("Expected subtype key is not the expected value." + errmsg());
185 break;
187 if (!serialized.is_object())
188 throw std::runtime_error("Expected keyed subtype to serialize as an object." + errmsg());
189 if (serialized.count(key))
190 throw std::runtime_error("Subtype key already present when trying to insert." + errmsg());
191 serialized[key] = val;
192 break;
193 default:
194 throw std::runtime_error("Unknown keyed_subtype_action.");
195 }
196 }
197
198 return serialized;
199 }
200
201private:
202 using create_function = std::function<TPointer (const extraction_context&, const value&)>;
203
204private:
205 using serialization_action = std::tuple<std::string, value, keyed_subtype_action>;
206
207 std::vector<std::pair<match_predicate, create_function>> _subtype_ctors;
208 std::map<std::type_index, serialization_action> _serialization_actions;
209 bool _check_null_input = false;
210 bool _check_null_output = false;
211};
212
213/// \}
214
215}
An adapter for the type T.
virtual void extract(const extraction_context &context, const value &from, void *into) const override
Extract a the type from a value into a region of memory.
Exception thrown if an insertion of an extractor or serializer into a formats is attempted,...
An adapter for enumeration types.
An adapter which can create polymorphic types.
void add_subtype_keyed(std::string key, value expected_value, keyed_subtype_action action=keyed_subtype_action::none)
Add a subtype which can be transformed into TPointer which will be called if given a JSON value with ...
void add_subtype(match_predicate pred)
Add a subtype which can be transformed into TPointer which will be called if the discriminator pred i...
Represents a single JSON value, which can be any one of a potential kind, each behaving slightly diff...
Definition value.hpp:107
object_iterator end_object()
Get an iterator to the one past the end of this object.
object_iterator find(const std::string &key)
Attempt to locate a key-value pair with the provided key in this object.
bool is_object() const
Tests if this kind is kind::object.
Copyright (c) 2014-2020 by Travis Gockel.
JSONV_PUBLIC std::string demangle(string_view source)
Convert the input source from a mangled type into a human-friendly version.
keyed_subtype_action
What to do when serializing a keyed subtype of a polymorphic_adapter.
@ check
Ensure the correct key/value pair was inserted by serialization. Throws std::runtime_error if it wasn...
@ none
Don't do any checking or insertion of the expected key/value pair.
@ insert
Insert the correct key/value pair as part of serialization.
JSONV_PUBLIC const value null
An instance with kind::null.
Conversion between C++ types and JSON values.