ElementJsonSerializers.h
Go to the documentation of this file.
1#pragma once
2
3#include <functional>
4
5#include <SimoxUtility/json.h>
6#include <SimoxUtility/meta/type_name.h>
7
8#include <RobotAPI/interface/ArViz/Elements.h>
9
10#include "exceptions.h"
11
12namespace armarx::viz::data
13{
14 /// @see `ElementSerializers::to_json()`
15 void to_json(nlohmann::json& j, const Element& element);
16 /// @see `ElementSerializers::from_json()`
17 void from_json(const nlohmann::json& j, Element& element);
18
19 /// @see `ElementSerializers::to_json()`
20 void to_json(nlohmann::json& j, const ElementPtr& elementPtr);
21 /// @see `ElementSerializers::from_json()`
22 void from_json(const nlohmann::json& j, ElementPtr& elementPtr);
23
24 /// @see `ElementSerializers::to_json()`
25 void to_json(nlohmann::json& j, const Element* elementPtr);
26} // namespace armarx::viz::data
27
28namespace armarx::viz::json
29{
30 /**
31 * @brief Get the type name stored in `j`.
32 * @param key The key of the type name entry.
33 * @throw `error::NoTypeNameEntryInJsonObject`
34 * If `j` is not an object or does not contain `key`.
35 */
36 std::string getTypeName(const nlohmann::json& j, const std::string& key);
37
38
39 /**
40 * @brief Store the type name in `j`.
41 * @param key The key where the type name shall be stored.
42 * @param typeName The type name.
43 *
44 * @throws `error::TypeNameEntryAlreadyInJsonObject`
45 * If `j` already contains `key`.
46 */
47 void setTypeName(nlohmann::json& j, const std::string& key, const std::string& typeName);
48
49
50 /**
51 * A raw function pointer to a function with signature:
52 * @code
53 * void to_json(nlohmann::json& j, const Value& v);
54 * @endcode
55 */
56 template <class ValueT>
57 using RawToJsonFn = void (*)(nlohmann::json& j, const ValueT& v);
58 /**
59 * A raw function pointer to a function with signature:
60 * @code
61 * void from_json(const nlohmann::json& j, Value& v);
62 * @endcode
63 */
64 template <class ValueT>
65 using RawFromJsonFn = void (*)(const nlohmann::json& j, ValueT& v);
66
67
68 /**
69 * A `std::function` pointer to a function with signature:
70 * @code
71 * void to_json(nlohmann::json& j, const Value& v);
72 * @endcode
73 */
74 template <class ValueT>
75 using ToJsonFn = std::function<void(nlohmann::json& j, const ValueT& v)>;
76 /**
77 * A `std::function` pointer to a function with signature:
78 * @code
79 * void from_json(const nlohmann::json& j, Value& v);
80 * @endcode
81 */
82 template <class ValueT>
83 using FromJsonFn = std::function<void(const nlohmann::json& j, ValueT& v)>;
84
85 /**
86 * @brief Handles serialization and deserialization of dynamic `data::Element`
87 * objects to and from JSON.
88 *
89 * This class allows serialization of newly defined types through the
90 * standard `nlohmann::json` interface, which builds upon user-defined
91 * `to_json()` and `from_json()` functions for a user-defined type.
92 *
93 * @section How to register your custom data::Element type
94 *
95 * Suppose you have a custom class deriving from `semrel::data::Element` in a
96 * namespace `myelement`, along with serializing functions following the
97 * standard pattern employed by `nlohmann::json`:
98 * @code
99 * namespace myelement
100 * {
101 * class Mydata::Element : public semrel::data::Element { ... };
102 *
103 * void to_json(nlohmann::json& j, const Mydata::Element& element);
104 * void from_json(const nlohmann::json& j, Mydata::Element& element);
105 * }
106 * @endcode
107 *
108 * To enable serialization through the general serialization functions
109 * for `data::Element`, register your functions to `ElementSerializers`, specifying
110 * your custom type as template argument:
111 * @code
112 * // in namespace myelement
113 * json::ElementSerializers::registerSerializer<Mydata::Element>(to_json, from_json);
114 * @endcode
115 * You can also specify the namespace, like in `myelement::to_json`.
116 *
117 * To make sure the serializer is always registered, you can create a
118 * static variable (extern free or a static class member) whose
119 * initialization will register the serializers, e.g.:
120 * @code
121 * // In the header file:
122 * namespace myelement
123 * {
124 * // Extern free or static member variable.
125 * extern const int _MY_SHAPE_JSON_REGISTRATION;
126 * }
127 *
128 * // In the sourcer file:
129 * const int myelement::_MY_SHAPE_JSON_REGISTRATION = []()
130 * {
131 * // Register serializer when initializing variable.
132 * json::ElementSerializers::registerSerializer<Mydata::Element>(to_json, from_json);
133 * return 0;
134 * }();
135 * @endcode
136 *
137 *
138 * @section How it is done
139 *
140 * `ElementSerializers` has a global map, which maps a (demangled) type name
141 * to the respective serializer. When serializing an object of type
142 * `data::Element`, the map is searched for an entry for the instance's dynamic
143 * type. If one is found, the registered `to_json()` is used to write the
144 * JSON document. In addition, a type name entry specifying the (demangled)
145 * derived type name is stored (under the key
146 * `ElementSerializers::JSON_TYPE_NAME_KEY`).
147 *
148 * When deserializing from a JSON document, the type name entry is used to
149 * determine the correct serializer, which casts the to-be-deserialized
150 * `data::Element` instance to the derived type and passes it to the registered
151 * `from_json` method. When deserializing into a `data::ElementPtr`, the pointer
152 * is allocated a new instance of the deriving type first.
153 */
154 class ElementJsonSerializers
155 {
156 public:
157 /// JSON key under which demangled type name is stored.
158 static const std::string JSON_TYPE_NAME_KEY;
159
160
161 public:
162 // PUBLIC STATIC INTERFACE
163
164 /**
165 * @brief Register a JSON seralizer for `DerivedElement`.
166 *
167 * Can be called with raw function pointers with automatic type deduction e.g.:
168 *
169 * @code
170 * namespace my_element {
171 * // Standard serialization methods.
172 * void to_json(nlohmann::json& j, const Mydata::Element& box);
173 * void from_json(const nlohmann::json& j, Mydata::Element& box);
174 * }
175 *
176 * void register() {
177 * // Register serializer:
178 * registerSerializer<my_element::Mydata::Element>(my_element::to_json, my_element::from_json);
179 * }
180 * @endcode
181 *
182 * @throw `error::SerializerAlreadyRegisteredForType`
183 * If there already is a registered serializer for that type.
184 */
185 template <class DerivedElement>
186 static void
195
196 /**
197 * @brief Register a JSON seralizer for `DerivedElement`.
198 *
199 * Can be called with `std::function` objects, e.g. lambdas:
200 * @code
201 * // Capture `serializer` by reference.
202 * registerSerializer<Box>(
203 * [&](nlohmann::json& j, const Box& box) { myserializer.to_json(j, box); },
204 * [&](const nlohmann::json& j, Box& box) { myserializer.from_json(j, cyl); }
205 * );
206 * @endcode
207 *
208 * @throw `error::SerializerAlreadyRegisteredForType`
209 * If there already is a registered serializer for that type.
210 */
211 template <class DerivedElement>
212 static void
215 bool overwrite = false)
216 {
217 _instance._registerSerializer<DerivedElement>(to_json, from_json, overwrite);
218 }
219
220 /**
221 * Remove a registered serializer for `DerivedElement`.
222 */
223 template <class DerivedElement>
224 static void
226 {
227 _instance._removeSerializer<DerivedElement>();
228 }
229
230 /// Get the type names for which serializers are registered.
231 static std::vector<std::string> getRegisteredTypes();
232
233 /// Indicates whether there is a serializer registered for the given type name.
234 static bool isTypeRegistered(const std::string& typeName);
235
236
237 /**
238 * @brief Serialize `element` to JSON according to its dynamic type.
239 * @throw `error::NoSerializerForType` If there is no serializer for the given name.
240 */
241 static void to_json(nlohmann::json& j, const data::Element& element);
242 /**
243 * @brief Deserialize `element` from JSON according to its dynamic type.
244 * @throws `error::NoTypeNameEntryInJsonObject`
245 * If `j` does not contain the key `JSON_TYPE_NAME_KEY` (or is not a JSON object).
246 * @throws `error::TypeNameMismatch`
247 * If the type name in `j` does not match `element`'s dynamic type.
248 * @throws `error::NoSerializerForType`
249 * If there is no serializer for the given name.
250 */
251 static void from_json(const nlohmann::json& j, data::Element& element);
252
253 /**
254 * @brief Serialize `element` to JSON according to its dynamic type.
255 * @throw `error::data::ElementPointerIsNull` If `elementPtr` is null.
256 * @throw `error::NoSerializerForType`
257 * If there is no serializer for the element type.
258 * @see `to_json(nlohmann::json& j, const data::Element& element)`
259 */
260 static void to_json(nlohmann::json& j, const data::Element* elementPtr);
261 /**
262 * @brief Deserialize `elementPtr` from JSON according the type name in `j`.
263 * If there is a registered serializer for the type name in `j`,
264 * assigns a new instance of the dynamic type to `elementPtr` and
265 * deserializes the created instance from `j`.
266 * /// Get the accepted demangled type names.
267
268 * @throws `error::NoTypeNameEntryInJsonObject`
269 * If `j` does not contain the key `JSON_TYPE_NAME_KEY` (or is not a JSON object).
270 * @throw `error::NoSerializerForType`
271 * If there is no serializer for the type in `j`.
272 */
273 static void from_json(const nlohmann::json& j, data::ElementPtr& elementPtr);
274
275 static std::string getTypeName(const nlohmann::json& j);
276
277
278 private:
279 // PRIVATE STATIC INTERFACE
280
281 /// A serializer for a specific derived `data::Element` type.
282 struct ElementJsonSerializer
283 {
284 template <class DerivedElement>
285 ElementJsonSerializer(ToJsonFn<DerivedElement> to_json,
287 {
288 _to_json = [to_json](nlohmann::json& j, const data::Element& element)
289 {
290 to_json(j, *dynamic_cast<const DerivedElement*>(&element));
292 j, JSON_TYPE_NAME_KEY, simox::meta::get_type_name<DerivedElement>());
293 };
294 _from_json = [this, from_json](const nlohmann::json& j, data::Element& element)
295 { from_json(j, *dynamic_cast<DerivedElement*>(&element)); };
296 _from_json_ptr =
297 [this, from_json](const nlohmann::json& j, data::ElementPtr& elementPtr)
298 {
299 // Referencing this->from_json() here causes a memory access violation.
300 // Therefore we use the from_json argument.
301 elementPtr = {new DerivedElement()};
302 from_json(j, *dynamic_cast<DerivedElement*>(elementPtr.get()));
303 // this->_from_json(j, *elementPtr)); // Causes memory access violation.
304 };
305 }
306
307 void to_json(nlohmann::json& j, const data::Element& element);
308 void from_json(const nlohmann::json& j, data::Element& element);
309
310 void to_json(nlohmann::json& j, const data::ElementPtr& elementPtr);
311 void from_json(const nlohmann::json& j, data::ElementPtr& elementPtr);
312
313
314 public:
316 FromJsonFn<data::Element> _from_json;
317 FromJsonFn<data::ElementPtr> _from_json_ptr;
318 };
319
320 static ElementJsonSerializer& getSerializer(const nlohmann::json& j);
321 static ElementJsonSerializer& getSerializer(const std::string& demangledTypeName);
322
323
324 /// The instance, holding the registered serializers.
325 static ElementJsonSerializers _instance;
326
327 static void registerElements();
328
329
330 private:
331 // PRIVATE NON-STATIC INTERFACE
332
333 /**
334 * @brief Construct the instance.
335 *
336 * When initialized, serializers for builtin types are registered
337 * via `registerElements()`.
338 */
339 ElementJsonSerializers();
340
341 /**
342 * @brief Register a serializer for `DerivedElement`.
343 * @throw `error::SerializerAlreadyRegisteredForType`
344 * If there already is a registered serializer for that type.
345 */
346 template <class DerivedElement>
347 void
348 _registerSerializer(ToJsonFn<DerivedElement> to_json,
350 bool overwrite)
351 {
352 const std::string typeName = simox::meta::get_type_name<DerivedElement>();
353 if (!overwrite && _serializers.count(typeName))
354 {
355 throw error::SerializerAlreadyRegisteredForType(typeName, getRegisteredTypes());
356 }
357 _serializers.emplace(typeName, ElementJsonSerializer(to_json, from_json));
358 }
359
360 /**
361 * @brief Remove the serializer for `DerivedElement`.
362 */
363 template <class DerivedElement>
364 void
365 _removeSerializer()
366 {
367 _serializers.erase(simox::meta::get_type_name<DerivedElement>());
368 }
369
370 /**
371 * @brief Get the serializer for the given demangled type name.
372 * @throw `error::NoSerializerForType` If there is no serializer for the given name.
373 */
374 ElementJsonSerializer& _getSerializer(const std::string& demangledTypeName);
375
376 /// Get the type names for which serializers are registered.
377 std::vector<std::string> _getRegisteredTypes() const;
378
379 /// Indicates whether there is a serializer registered for the given type name.
380 bool _isTypeRegistered(const std::string& typeName) const;
381
382
383 /// The serializers. Map of demangled type name to serializer.
384 std::map<std::string, ElementJsonSerializer> _serializers;
385 };
386
387} // namespace armarx::viz::json
static void registerSerializer(ToJsonFn< DerivedElement > to_json, FromJsonFn< DerivedElement > from_json, bool overwrite=false)
Register a JSON seralizer for DerivedElement.
static void from_json(const nlohmann::json &j, data::Element &element)
Deserialize element from JSON according to its dynamic type.
static const std::string JSON_TYPE_NAME_KEY
JSON key under which demangled type name is stored.
static void to_json(nlohmann::json &j, const data::Element &element)
Serialize element to JSON according to its dynamic type.
static std::vector< std::string > getRegisteredTypes()
Get the type names for which serializers are registered.
static std::string getTypeName(const nlohmann::json &j)
static void registerSerializer(RawToJsonFn< DerivedElement > to_json, RawFromJsonFn< DerivedElement > from_json, bool overwrite=false)
Register a JSON seralizer for DerivedElement.
static bool isTypeRegistered(const std::string &typeName)
Indicates whether there is a serializer registered for the given type name.
static void removeSerializer()
Remove a registered serializer for DerivedElement.
void to_json(nlohmann::json &j, RecordingBatchHeader const &batch)
void from_json(nlohmann::json const &j, RecordingBatchHeader &batch)
std::function< void(nlohmann::json &j, const ValueT &v)> ToJsonFn
A std::function pointer to a function with signature:
void(*)(nlohmann::json &j, const ValueT &v) RawToJsonFn
A raw function pointer to a function with signature:
std::string getTypeName(const nlohmann::json &j, const std::string &key)
Get the type name stored in j.
void(*)(const nlohmann::json &j, ValueT &v) RawFromJsonFn
A raw function pointer to a function with signature:
std::function< void(const nlohmann::json &j, ValueT &v)> FromJsonFn
A std::function pointer to a function with signature:
void setTypeName(nlohmann::json &j, const std::string &key, const std::string &typeName)
Store the type name in j.
void from_json(const nlohmann::json &j, Vector2f &value)
void to_json(nlohmann::json &j, const Vector2f &value)