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 namespace 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 
28 namespace 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  */
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
189  bool overwrite = false)
190  {
191  registerSerializer<DerivedElement>(ToJsonFn<DerivedElement>(to_json),
193  overwrite);
194  }
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));
291  setTypeName(
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:
315  ToJsonFn<data::Element> _to_json;
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,
349  FromJsonFn<DerivedElement> from_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
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:117
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:75
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:23
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:213
armarx::viz::data::from_json
void from_json(nlohmann::json const &j, RecordingBatchHeader &batch)
Definition: ArVizStorage.cpp:432
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:158
armarx::viz::data
Definition: ArVizStorage.cpp:418
armarx::viz::json
Definition: ElementJsonSerializers.cpp:63
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:83
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:57
armarx::viz::json::ElementJsonSerializers::getTypeName
static std::string getTypeName(const nlohmann::json &j)
Definition: ElementJsonSerializers.cpp:157
armarx::viz::json::RawFromJsonFn
void(*)(const nlohmann::json &j, ValueT &v) RawFromJsonFn
A raw function pointer to a function with signature:
Definition: ElementJsonSerializers.h:65
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:187
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:154
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:123
armarx::viz::json::ElementJsonSerializers::getRegisteredTypes
static std::vector< std::string > getRegisteredTypes()
Get the type names for which serializers are registered.
Definition: ElementJsonSerializers.cpp:111
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:129
armarx::viz::data::to_json
void to_json(nlohmann::json &j, RecordingBatchHeader const &batch)
Definition: ArVizStorage.cpp:422