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