JSON Voorhees
Killer JSON for C++
serialization_util.hpp
Go to the documentation of this file.
1 /** \file jsonv/serialization_util.hpp
2  * Helper types and functions for serialization. These are usually not needed unless you are writing your own
3  * \c extractor or \c serializer.
4  *
5  * Copyright (c) 2015 by Travis Gockel. All rights reserved.
6  *
7  * This program is free software: you can redistribute it and/or modify it under the terms of the Apache License
8  * as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later
9  * version.
10  *
11  * \author Travis Gockel (travis@gockelhut.com)
12 **/
13 #ifndef __JSONV_SERIALIZATION_UTIL_HPP_INCLUDED__
14 #define __JSONV_SERIALIZATION_UTIL_HPP_INCLUDED__
15 
16 #include <jsonv/config.hpp>
17 #include <jsonv/demangle.hpp>
18 #include <jsonv/functional.hpp>
19 #include <jsonv/serialization.hpp>
20 
21 #include <initializer_list>
22 #include <functional>
23 #include <map>
24 #include <type_traits>
25 
26 namespace jsonv
27 {
28 
29 /** \addtogroup Serialization
30  * \{
31 **/
32 
33 template <typename T>
35  public extractor
36 {
37 public:
38  virtual const std::type_info& get_type() const override
39  {
40  return typeid(T);
41  }
42 
43  virtual void extract(const extraction_context& context,
44  const value& from,
45  void* into
46  ) const override
47  {
48  return extract_impl<T>(context, from, into);
49  }
50 
51 protected:
52  template <typename U>
53  auto extract_impl(const extraction_context& context,
54  const value& from,
55  void* into
56  ) const
57  -> decltype(U(from, context), void())
58  {
59  new(into) U(from, context);
60  }
61 
62  template <typename U, typename = void>
63  auto extract_impl(const extraction_context&,
64  const value& from,
65  void* into
66  ) const
67  -> decltype(U(from), void())
68  {
69  new(into) U(from);
70  }
71 };
72 
73 template <typename T>
75  public extractor
76 {
77 public:
78  virtual const std::type_info& get_type() const override
79  {
80  return typeid(T);
81  }
82 
83  virtual void extract(const extraction_context& context,
84  const value& from,
85  void* into
86  ) const override
87  {
88  new(into) T(create(context, from));
89  }
90 
91 protected:
92  virtual T create(const extraction_context& context, const value& from) const = 0;
93 };
94 
95 template <typename T, typename FExtract>
97  public extractor_for<T>
98 {
99 public:
100  template <typename FUExtract>
101  explicit function_extractor(FUExtract&& func) :
102  _func(std::forward<FUExtract>(func))
103  { }
104 
105 protected:
106  virtual T create(const extraction_context& context, const value& from) const override
107  {
108  return create_impl(_func, context, from);
109  }
110 
111 private:
112  template <typename FUExtract>
113  static auto create_impl(const FUExtract& func, const extraction_context& context, const value& from)
114  -> decltype(func(context, from))
115  {
116  return func(context, from);
117  }
118 
119  template <typename FUExtract, typename = void>
120  static auto create_impl(const FUExtract& func, const extraction_context&, const value& from)
121  -> decltype(func(from))
122  {
123  return func(from);
124  }
125 
126 private:
127  FExtract _func;
128 };
129 
130 template <typename FExtract>
131 auto make_extractor(FExtract func)
133  FExtract
134  >
135 {
136  return function_extractor<decltype(func(std::declval<const extraction_context&>(), std::declval<const value&>())),
137  FExtract
138  >
139  (std::move(func));
140 }
141 
142 template <typename FExtract, typename = void>
143 auto make_extractor(FExtract func)
145  FExtract
146  >
147 {
149  (std::move(func));
150 }
151 
152 template <typename T>
154  public serializer
155 {
156 public:
157  virtual const std::type_info& get_type() const override
158  {
159  return typeid(T);
160  }
161 
162  virtual value to_json(const serialization_context& context,
163  const void* from
164  ) const override
165  {
166  return to_json(context, *static_cast<const T*>(from));
167  }
168 
169 protected:
170  virtual value to_json(const serialization_context& context,
171  const T& from
172  ) const = 0;
173 };
174 
175 template <typename T, typename FToJson>
177  public serializer_for<T>
178 {
179 public:
180  template <typename FUToJson>
181  explicit function_serializer(FUToJson&& to_json_) :
182  _to_json(std::forward<FUToJson>(to_json_))
183  { }
184 
185 protected:
186 
187  virtual value to_json(const serialization_context& context, const T& from) const override
188  {
189  return to_json_impl(_to_json, context, from);
190  }
191 
192 private:
193  template <typename FUToJson>
194  static auto to_json_impl(const FUToJson& func, const serialization_context& context, const T& from)
195  -> decltype(func(context, from))
196  {
197  return func(context, from);
198  }
199 
200  template <typename FUToJson, typename = void>
201  static auto to_json_impl(const FUToJson& func, const serialization_context&, const T& from)
202  -> decltype(func(from))
203  {
204  return func(from);
205  }
206 
207 private:
208  FToJson _to_json;
209 };
210 
211 template <typename T, typename FToJson>
212 function_serializer<T, FToJson> make_serializer(FToJson to_json_)
213 {
214  return function_serializer<T, FToJson>(std::move(to_json_));
215 }
216 
217 template <typename T>
218 class adapter_for :
219  public adapter
220 {
221 public:
222  virtual const std::type_info& get_type() const override
223  {
224  return typeid(T);
225  }
226 
227  virtual void extract(const extraction_context& context,
228  const value& from,
229  void* into
230  ) const override
231  {
232  new(into) T(create(context, from));
233  }
234 
235  virtual value to_json(const serialization_context& context,
236  const void* from
237  ) const override
238  {
239  return to_json(context, *static_cast<const T*>(from));
240  }
241 
242 protected:
243  virtual T create(const extraction_context& context, const value& from) const = 0;
244 
245  virtual value to_json(const serialization_context& context, const T& from) const = 0;
246 };
247 
248 template <typename T, typename FExtract, typename FToJson>
250  public adapter_for<T>
251 {
252 public:
253  template <typename FUExtract, typename FUToJson>
254  explicit function_adapter(FUExtract&& extract_, FUToJson&& to_json_) :
255  _extract(std::forward<FUExtract>(extract_)),
256  _to_json(std::forward<FUToJson>(to_json_))
257  { }
258 
259 protected:
260  virtual T create(const extraction_context& context, const value& from) const override
261  {
262  return create_impl(_extract, context, from);
263  }
264 
265  virtual value to_json(const serialization_context& context, const T& from) const override
266  {
267  return to_json_impl(_to_json, context, from);
268  }
269 
270 private:
271  template <typename FUExtract>
272  static auto create_impl(const FUExtract& func, const extraction_context& context, const value& from)
273  -> decltype(func(context, from))
274  {
275  return func(context, from);
276  }
277 
278  template <typename FUExtract, typename = void>
279  static auto create_impl(const FUExtract& func, const extraction_context&, const value& from)
280  -> decltype(func(from))
281  {
282  return func(from);
283  }
284 
285  template <typename FUToJson>
286  static auto to_json_impl(const FUToJson& func, const serialization_context& context, const T& from)
287  -> decltype(func(context, from))
288  {
289  return func(context, from);
290  }
291 
292  template <typename FUToJson, typename = void>
293  static auto to_json_impl(const FUToJson& func, const serialization_context&, const T& from)
294  -> decltype(func(from))
295  {
296  return func(from);
297  }
298 
299 private:
300  FExtract _extract;
301  FToJson _to_json;
302 };
303 
304 template <typename FExtract, typename FToJson>
305 auto make_adapter(FExtract extract, FToJson to_json_)
307  FExtract,
308  FToJson
309  >
310 {
311  return function_adapter<decltype(extract(std::declval<const extraction_context&>(), std::declval<const value&>())),
312  FExtract,
313  FToJson
314  >
315  (std::move(extract), std::move(to_json_));
316 }
317 
318 template <typename FExtract, typename FToJson, typename = void>
319 auto make_adapter(FExtract extract, FToJson to_json_)
321  FExtract,
322  FToJson
323  >
324 {
326  FExtract,
327  FToJson
328  >
329  (std::move(extract), std::move(to_json_));
330 }
331 
332 /** An adapter for optional-like types. This is for convenience of creating an \c adapter for things like
333  * \c std::optional or \c boost::optional.
334  *
335  * \tparam TOptional The optional container type. It must have a member type named \c value_type which holds the
336  * actual type. It must default-construct to the "none" type and have a single-argument constructor which takes a
337  * \c TOptional::value_type. It must support a boolean conversion operator for checking if the value is none and a
338  * unary \c operator* for getting the underlying value. Both \c std::optional and \c boost::optional possess all of
339  * these properties.
340 **/
341 template <typename TOptional>
343  public adapter_for<TOptional>
344 {
345  using element_type = typename TOptional::value_type;
346 
347 protected:
348  virtual TOptional create(const extraction_context& context, const value& from) const override
349  {
350  if (from.is_null())
351  return TOptional();
352  else
353  return TOptional(context.extract<element_type>(from));
354  }
355 
356  virtual value to_json(const serialization_context& context, const TOptional& from) const override
357  {
358  if (from)
359  return context.to_json(*from);
360  else
361  return value();
362  }
363 };
364 
365 /** An adapter for container types. This is for convenience of creating an \c adapter for things like \c std::vector,
366  * \c std::set and such.
367  *
368  * \tparam TContainer is the container to create and encode. It must have a member type \c value_type, support
369  * iteration and an \c insert operation.
370 **/
371 template <typename TContainer>
373  public adapter_for<TContainer>
374 {
375  using element_type = typename TContainer::value_type;
376 
377 protected:
378  virtual TContainer create(const extraction_context& context, const value& from) const override
379  {
380  using std::end;
381 
382  TContainer out;
383  from.as_array(); // get nice error if input is not an array
384  for (value::size_type idx = 0U; idx < from.size(); ++idx)
385  out.insert(end(out), context.extract_sub<element_type>(from, idx));
386  return out;
387  }
388 
389  virtual value to_json(const serialization_context& context, const TContainer& from) const override
390  {
391  value out = array();
392  for (const element_type& x : from)
393  out.push_back(context.to_json(x));
394  return out;
395  }
396 };
397 
398 /** An adapter for "wrapper" types.
399  *
400  * \tparam TWrapper A wrapper type with a member \c value_type which represents the underlying wrapped type. This must
401  * be explicitly convertible to and from the \c value_type.
402 **/
403 template <typename TWrapper>
405  public adapter_for<TWrapper>
406 {
407  using element_type = typename TWrapper::value_type;
408 
409 protected:
410  virtual TWrapper create(const extraction_context& context, const value& from) const override
411  {
412  return TWrapper(context.extract<element_type>(from));
413  }
414 
415  virtual value to_json(const serialization_context& context, const TWrapper& from) const override
416  {
417  return context.to_json(element_type(from));
418  }
419 };
420 
421 /** An adapter for enumeration types. The most common use of this is to map \c enum values in C++ to string values in a
422  * JSON representation (and vice versa).
423  *
424  * \tparam TEnum The type to map. This is not restricted to C++ enumerations (types defined with the \c enum keyword),
425  * but any type you wish to restrict to a subset of values.
426  * \tparam FEnumComp <tt>bool (*)(TEnum, TEnum)</tt> -- a strict ordering for \c TEnum values.
427  * \tparam FValueComp <tt>bool (*)(value, value)</tt> -- a strict ordering for \c value objects. By default, this is a
428  * case-sensitive comparison, but this can be replaced with anything you desire (for example, use
429  * \c value_less_icase to ignore case in extracting from JSON).
430  *
431  * \see enum_adapter_icase
432 **/
433 template <typename TEnum,
434  typename FEnumComp = std::less<TEnum>,
435  typename FValueComp = std::less<value>
436  >
438  public adapter_for<TEnum>
439 {
440 public:
441  /** Create an adapter with mapping values from the range <tt>[first, last)</tt>.
442  *
443  * \tparam TForwardIterator An iterator yielding the type <tt>std::pair<jsonv::value, TEnum>></tt>
444  **/
445  template <typename TForwardIterator>
446  explicit enum_adapter(std::string enum_name, TForwardIterator first, TForwardIterator last) :
447  _enum_name(std::move(enum_name))
448  {
449  for (auto iter = first; iter != last; ++iter)
450  {
451  _val_to_cpp.insert({ iter->second, iter->first });
452  _cpp_to_val.insert(*iter);
453  }
454  }
455 
456  /** Create an adapter with the specified \a mapping values.
457  *
458  * \param enum_name A user-friendly name for this enumeration to be used in error messages.
459  * \param mapping A list of C++ types and values to use in \c to_json and \c extract. It is okay to have a C++
460  * value with more than one JSON representation. In this case, the \e first JSON representation will
461  * be used in \c to_json, but \e all JSON representations will be interpreted as the C++ value. It
462  * is also okay to have the same JSON representation for multiple C++ values. In this case, the
463  * \e first JSON representation provided for that value will be used in \c extract.
464  *
465  * \example "Serialization: Enum Adapter"
466  * \code
467  * enum_adapter<ring>("ring",
468  * {
469  * { ring::fire, "fire" },
470  * { ring::wind, "wind" },
471  * { ring::earth, "earth" },
472  * { ring::water, "water" },
473  * { ring::heart, "heart" }, // "heart" is preferred for to_json
474  * { ring::heart, "useless" }, // "useless" is interpreted as ring::heart in extract
475  * }
476  * );
477  * \endcode
478  **/
479  explicit enum_adapter(std::string enum_name, std::initializer_list<std::pair<TEnum, value>> mapping) :
480  enum_adapter(std::move(enum_name), mapping.begin(), mapping.end())
481  { }
482 
483 protected:
484  virtual TEnum create(const extraction_context& context, const value& from) const override
485  {
486  using std::end;
487 
488  auto iter = _val_to_cpp.find(from);
489  if (iter != end(_val_to_cpp))
490  return iter->second;
491  else
492  throw extraction_error(context,
493  std::string("Invalid value for ") + _enum_name + ": " + to_string(from)
494  );
495  }
496 
497  virtual value to_json(const serialization_context&, const TEnum& from) const override
498  {
499  using std::end;
500 
501  auto iter = _cpp_to_val.find(from);
502  if (iter != end(_cpp_to_val))
503  return iter->second;
504  else
505  return null;
506  }
507 
508 private:
509  std::string _enum_name;
510  std::map<value, TEnum, FValueComp> _val_to_cpp;
511  std::map<TEnum, value, FEnumComp> _cpp_to_val;
512 };
513 
514 /** An adapter for enumeration types which ignores the case when extracting from JSON.
515  *
516  * \see enum_adapter
517 **/
518 template <typename TEnum, typename FEnumComp = std::less<TEnum>>
520 
521 /**
522  * What to do when serializing a keyed subtype of a \ref polymorphic_adapter. See \ref
523  * polymorphic_adapter::add_subtype_keyed.
524 **/
525 enum class keyed_subtype_action : unsigned char
526 {
527  /** Don't do any checking or insertion of the expected key/value pair. **/
528  none,
529  /** Ensure the correct key/value pair was inserted by serialization. Throws \c std::runtime_error if it wasn't. **/
530  check,
531  /** Insert the correct key/value pair as part of serialization. Throws \c std::runtime_error if the key is already
532  * present.
533  **/
534  insert
535 };
536 
537 /** An adapter which can create polymorphic types. This allows you to parse JSON directly into a type heirarchy without
538  * some middle layer.
539  *
540  * @code
541  * [
542  * {
543  * "type": "worker",
544  * "name": "Adam"
545  * },
546  * {
547  * "type": "manager",
548  * "name": "Bob",
549  * "head": "Development"
550  * }
551  * ]
552  * @endcode
553  *
554  * \tparam TPointer Some pointer-like type (likely \c unique_ptr or \c shared_ptr) you wish to extract values into. It
555  * must support \c operator*, an explicit conversion to \c bool, construction with a pointer to a
556  * subtype of what it contains and default construction.
557 **/
558 template <typename TPointer>
560  public adapter_for<TPointer>
561 {
562 public:
563  using match_predicate = std::function<bool (const extraction_context&, const value&)>;
564 
565 public:
566  polymorphic_adapter() = default;
567 
568  /** Add a subtype which can be transformed into \c TPointer which will be called if the discriminator \a pred is
569  * matched.
570  *
571  * \see add_subtype_keyed
572  **/
573  template <typename T>
574  void add_subtype(match_predicate pred)
575  {
576  _subtype_ctors.emplace_back(std::move(pred),
577  [] (const extraction_context& context, const value& value)
578  {
579  return TPointer(new T(context.extract<T>(value)));
580  }
581  );
582  }
583 
584  /** Add a subtype which can be transformed into \c TPointer which will be called if given a JSON \c value with
585  * \c kind::object which has a member with \a key and the provided \a expected_value.
586  *
587  * \see add_subtype
588  **/
589  template <typename T>
590  void add_subtype_keyed(std::string key,
591  value expected_value,
593  {
594  std::type_index tidx = std::type_index(typeid(T));
595  if (!_serialization_actions.emplace(tidx, std::make_tuple(key, expected_value, action)).second)
596  throw duplicate_type_error("polymorphic_adapter subtype", std::type_index(typeid(T)));
597 
598  match_predicate op = [key, expected_value] (const extraction_context&, const value& value)
599  {
600  if (!value.is_object())
601  return false;
602  auto iter = value.find(key);
603  return iter != value.end_object()
604  && iter->second == expected_value;
605  };
606  return add_subtype<T>(op);
607  }
608 
609  /** When extracting a C++ value, should \c kind::null in JSON automatically become a default-constructed \c TPointer
610  * (which is usually the \c null representation)?
611  **/
612  void check_null_input(bool on)
613  {
614  _check_null_input = on;
615  }
616 
617  bool check_null_input() const
618  {
619  return _check_null_input;
620  }
621 
622  /** When converting with \c to_json, should a \c null input translate into a \c kind::null? **/
623  void check_null_output(bool on)
624  {
625  _check_null_output = on;
626  }
627 
628  bool check_null_output() const
629  {
630  return _check_null_output;
631  }
632 
633 protected:
634  virtual TPointer create(const extraction_context& context, const value& from) const override
635  {
636  using std::begin;
637  using std::end;
638 
639  if (_check_null_input && from.is_null())
640  return TPointer();
641 
642  auto iter = std::find_if(begin(_subtype_ctors), end(_subtype_ctors),
643  [&] (const std::pair<match_predicate, create_function>& pair)
644  {
645  return pair.first(context, from);
646  }
647  );
648  if (iter != end(_subtype_ctors))
649  return iter->second(context, from);
650  else
651  throw extraction_error(context,
652  std::string("No discriminators matched JSON value: ") + to_string(from)
653  );
654  }
655 
656  virtual value to_json(const serialization_context& context, const TPointer& from) const override
657  {
658  if (_check_null_output && !from)
659  return null;
660 
661  value serialized = context.to_json(typeid(*from), static_cast<const void*>(&*from));
662 
663  auto action_iter = _serialization_actions.find(std::type_index(typeid(*from)));
664  if (action_iter != _serialization_actions.end())
665  {
666  auto errmsg = [&]()
667  {
668  return " polymorphic_adapter<" + demangle(typeid(TPointer).name()) + ">"
669  "subtype(" + demangle(typeid(*from).name()) + ")";
670  };
671 
672  const std::string& key = std::get<0>(action_iter->second);
673  const value& val = std::get<1>(action_iter->second);
674  const keyed_subtype_action& action = std::get<2>(action_iter->second);
675 
676  switch (action)
677  {
679  break;
681  if (!serialized.is_object())
682  throw std::runtime_error("Expected keyed subtype to serialize as an object." + errmsg());
683  if (!serialized.count(key))
684  throw std::runtime_error("Expected subtype key not found." + errmsg());
685  if (serialized.at(key) != val)
686  throw std::runtime_error("Expected subtype key is not the expected value." + errmsg());
687  break;
689  if (!serialized.is_object())
690  throw std::runtime_error("Expected keyed subtype to serialize as an object." + errmsg());
691  if (serialized.count(key))
692  throw std::runtime_error("Subtype key already present when trying to insert." + errmsg());
693  serialized[key] = val;
694  break;
695  default:
696  throw std::runtime_error("Unknown keyed_subtype_action.");
697  }
698  }
699 
700  return serialized;
701  }
702 
703 private:
704  using create_function = std::function<TPointer (const extraction_context&, const value&)>;
705 
706 private:
707  using serialization_action = std::tuple<std::string, value, keyed_subtype_action>;
708 
709  std::vector<std::pair<match_predicate, create_function>> _subtype_ctors;
710  std::map<std::type_index, serialization_action> _serialization_actions;
711  bool _check_null_input = false;
712  bool _check_null_output = false;
713 };
714 
715 /** \} **/
716 
717 }
718 
719 #endif/*__JSONV_SERIALIZATION_UTIL_HPP_INCLUDED__*/
void add_subtype(match_predicate pred)
Add a subtype which can be transformed into TPointer which will be called if the discriminator pred i...
An adapter for container types.
object_iterator end_object()
Get an iterator to the one past the end of this object.
virtual const std::type_info & get_type() const override
Get the run-time type this extractor knows how to extract.
T extract_sub(const value &from, jsonv::path subpath) const
Attempt to extract a T from from.at_path(subpath) using the formats associated with this context...
Don&#39;t do any checking or insertion of the expected key/value pair.
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 ...
Copyright (c) 2015 by Travis Gockel.
Exception thrown if there is any problem running extract.
bool is_null() const
Tests if this kind is kind::null.
STL namespace.
virtual const std::type_info & get_type() const override
Get the run-time type this extractor knows how to extract.
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.
Copyright (c) 2014-2019 by Travis Gockel.
value & at(size_type idx)
Get the value in this array at the given idx.
An adapter is both an extractor and a serializer.
An extractor holds the method for converting a value into an arbitrary C++ type.
An adapter for optional-like types.
An adapter which can create polymorphic types.
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.
A serializer holds the method for converting an arbitrary C++ type into a value.
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.
JSONV_PUBLIC const value null
An instance with kind::null.
virtual const std::type_info & get_type() const override
Get the run-time type this extractor knows how to extract.
void check_null_output(bool on)
When converting with to_json, should a null input translate into a kind::null?
JSONV_PUBLIC std::string demangle(string_view source)
Convert the input source from a mangled type into a human-friendly version.
size_type count(const std::string &key) const
Check if the given key exists in this object.
keyed_subtype_action
What to do when serializing a keyed subtype of a polymorphic_adapter.
An adapter for enumeration types.
array_view as_array()&
View this instance as an array.
JSONV_PUBLIC value array()
Create an empty array value.
Conversion between C++ types and JSON values.
JSONV_PUBLIC std::string to_string(const parse_error::problem &p)
Get a string representation of a problem.
Insert the correct key/value pair as part of serialization.
virtual value to_json(const serialization_context &context, const void *from) const override
Create a value from the value in the given region of memory.
An adapter for "wrapper" types.
Exception thrown if an insertion of an extractor or serializer into a formats is attempted, but there is already an extractor or serializer for that type.
virtual const std::type_info & get_type() const override
Get the run-time type this serialize knows how to encode.
Ensure the correct key/value pair was inserted by serialization.
value to_json(const T &from, const formats &fmts)
Encode a JSON value from from using the provided fmts.
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.
enum_adapter(std::string enum_name, TForwardIterator first, TForwardIterator last)
Create an adapter with mapping values from the range [first, last).
value to_json(const T &from) const
Convenience function for converting a C++ object into a JSON value.
T extract(const value &from) const
Attempt to extract a T from from using the formats associated with this context.
virtual value to_json(const serialization_context &context, const void *from) const override
Create a value from the value in the given region of memory.
void check_null_input(bool on)
When extracting a C++ value, should kind::null in JSON automatically become a default-constructed TPo...
size_type size() const
Get the number of items in this value.
Represents a single JSON value, which can be any one of a potential kind, each behaving slightly diff...
Definition: value.hpp:131
void push_back(value item)
Push item to the back of this array.
A collection of function objects a la <functional>.