JSON Voorhees
Killer JSON for C++
Loading...
Searching...
No Matches
formats.hpp
Go to the documentation of this file.
1/// \file jsonv/serialization/formats.hpp
2///
3/// Copyright (c) 2015-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>
13#include <jsonv/forward.hpp>
14#include <jsonv/path.hpp>
16
17#include <expected>
18#include <memory>
19#include <stdexcept>
20#include <string>
21#include <string_view>
22#include <typeindex>
23#include <typeinfo>
24#include <vector>
25
26namespace jsonv
27{
28
29/// \addtogroup Serialization
30/// \{
31
32/// The action to take when an insertion of a deserializer or serializer into a formats is attempted, but there is
33/// alredy a deserializer or serializer for that type.
34enum class duplicate_type_action : unsigned char
35{
36 /// The existing deserializer or serializer should be kept, but no exception should be thrown.
37 ignore,
38 /// The new deserializer or serializer should be inserted, and no exception should be thrown.
39 replace,
40 /// A \ref duplicate_type_error should be thrown.
42};
43
44/// Exception thrown if an insertion of a deserializer or serializer into a formats is attempted, but there is already a
45/// deserializer or serializer for that type.
47 public std::invalid_argument
48{
49public:
50 explicit duplicate_type_error(const std::string& operation, const std::type_index& type);
51
52 virtual ~duplicate_type_error() noexcept override;
53
55 const std::type_index& type_index() const { return _type_index; }
56
57private:
58 std::type_index _type_index;
59};
60
61/// Thrown when \c formats::deserialize does not have a \c deserializer for the provided type.
63 public std::runtime_error
64{
65public:
66 /// \{
67
68 /// Create a new exception.
69 explicit no_deserializer(const std::type_info& type);
70 explicit no_deserializer(const std::type_index& type);
71 /// \}
72
73 virtual ~no_deserializer() noexcept;
74
75 /// The name of the type.
77 std::string_view type_name() const;
78
79 /// Get an ID for the type of \c deserializer that \c formats::deserialize could not locate.
81 std::type_index type_index() const;
82
83private:
84 std::type_index _type_index;
85 std::string _type_name;
86};
87
88/// Thrown when \c formats::serialize does not have a \c serializer for the type it was asked to serialize.
89///
90/// Its \c serialization_error::path is where in the document the value was wanted. It is empty when the exception came
91/// from a lookup which was not writing anything, such as \c formats::get_serializer.
94{
95public:
96 /// \{
97
98 /// Create a new exception for the missing \a type, wanted at \a path.
99 explicit no_serializer(const std::type_info& type, jsonv::path path = jsonv::path());
100 explicit no_serializer(const std::type_index& type, jsonv::path path = jsonv::path());
101 /// \}
102
103 virtual ~no_serializer() noexcept;
104};
105
106/// Simply put, this class is a collection of \c deserializer and \c serializer instances.
107///
108/// Ultimately, \c formats form a directed graph of possible types to load. This allows you to compose formats including
109/// your application with any number of 3rd party libraries an base formats. Imagine an application that has both a user
110/// facing API and an object storage system, both of which speak JSON. When loading from the database, you would like
111/// strict type enforcement -- check that when you say <tt>deserialize&lt;int&gt;(val)</tt>, that \c val actually has
112/// \c kind::integer. When getting values from the API, you happily convert \c kind::string with the value \c "51" into
113/// the integer \c 51. You really don't want to have to write two versions of decoders for all of your objects: one
114/// which uses the standard checked \c value::as_integer and one that uses \c coerce_integer -- you want the same object
115/// model.
116///
117/// \dot
118/// digraph formats {
119/// defaults [label="formats::defaults"]
120/// coerce [label="formats::coerce"]
121/// lib [label="some_3rd_party"]
122/// my_app [label="my_app_formats"]
123/// db [label="my_app_database_loader"]
124/// api [label="my_app_api_loader"]
125///
126/// my_app -> lib
127/// db -> defaults
128/// db -> my_app
129/// api -> coerce
130/// api -> my_app
131/// }
132/// \enddot
133///
134/// To do this, you would create your object model (called \c my_app_formats in the chart) with the object models for
135/// your application-specific types. This can use any number of 3rd party libraries to get the job done. To make a
136/// functional \c formats, you would \c compose different \c formats instances into a single one. From there,
137///
138/// \code
139/// jsonv::formats get_api_formats()
140/// {
141/// static jsonv::formats instance = jsonv::formats::compose({ jsonv::formats::coerce(), get_app_formats() });
142/// return instance;
143/// }
144///
145/// jsonv::formats get_db_formats()
146/// {
147/// static jsonv::formats instance = jsonv::formats::compose({ jsonv::formats::defaults(), get_app_formats() });
148/// return instance;
149/// }
150///
151/// MyType deserialize_thing(const jsonv::value& from, bool from_db)
152/// {
153/// return jsonv::deserialize<MyType>(from, from_db ? get_db_formats() : get_api_formats());
154/// }
155/// \endcode
157{
158public:
159 using list = std::vector<formats>;
160
161public:
162 /// Get the default \c formats instance. This uses \e strict type-checking and behaves by the same rules as the
163 /// \c value \c as_ member functions (\c as_integer, \c as_string, etc): the JSON node has to be the kind the C++
164 /// type is built from.
165 ///
166 /// Range is the one place the built-ins are stricter than those accessors. An integer literal which does not fit
167 /// the destination is reported as a problem rather than read as a \c std::int64_t and then wrapped into it, so
168 /// \c 999 is not a \c std::uint8_t and \c -1 is not a \c std::uint64_t.
169 ///
170 /// A \c std::string_view is deserialized as a view of the source rather than a copy, so the source has to outlive
171 /// it: the caller's \c value for an in-memory deserialization, the JSON text for a \c reader over one. A string the
172 /// source spelt with escape sequences has no decoded form in it to view and is refused, as is one belonging to a
173 /// source which is freed when deserialization finishes -- a tree the pipeline materialised, or text handed to
174 /// \c jsonv::deserialize to own. See \c deserialization_context::source_is_temporary.
175 ///
176 /// \note
177 /// This function actually returns a \e copy of the default \c formats, so modifications do not affect the actual
178 /// instance.
181
182 /// Get the global \c formats instance. By default, this is the same as \c defaults, but you can override it with \c
183 /// set_global. The \c deserialize function uses this \c formats instance if none is provided, so this is convenient
184 /// if your application only has one type of \c formats to use.
185 ///
186 /// \note
187 /// This function actually returns a \e copy of the global \c formats, so modifications do not affect the actual
188 /// instance. If you wish to alter the global formats, use \c set_global.
190 static formats global();
191
192 /// Set the \c global \c formats instance.
193 ///
194 /// \returns the previous value of the global formats instance.
196
197 /// Reset the \c global \c formats instance to \c defaults.
198 ///
199 /// \returns the previous value of the global formats instance.
201
202 /// Get the coercing \c formats instance. This uses \e loose type-checking and behaves by the same rules as the
203 /// \c coerce_ functions in \c coerce.hpp: a string holding \c "10" deserializes as the integer \c 10, an integer
204 /// deserializes as the string \c "5", and every kind has a truth value -- \c kind::null's is \c false. It is
205 /// composed over \c defaults, so anything it does not redefine -- \c value and \c std::string_view among them --
206 /// behaves as it does there, range checking included.
207 ///
208 /// \note
209 /// This function actually returns a \e copy of the default \c formats, so modifications do not affect the actual
210 /// instance.
212 static formats coerce();
213
214 /// Create a new, empty \c formats instance. By default, this does not know how to deserialize anything -- not even
215 /// the basic types like \c int64_t or \c std::string.
217
218 /// \{
219
220 /// Copying a \c formats shares the node in the graph rather than duplicating it, so \c register_ calls through
221 /// either one are seen by both.
222 ///
223 /// Moving transfers the node and leaves the source empty: it finds no \c deserializer or \c serializer, a
224 /// \c register_ call starts a new node, and \c compose adds nothing for it. A moved-from \c formats is equal only
225 /// to another one in that state, since it is no node in the graph.
226 formats(const formats&) = default;
227 formats(formats&&) noexcept = default;
228 formats& operator=(const formats&) = default;
229 formats& operator=(formats&&) noexcept = default;
230 /// \}
231
232 ~formats() noexcept;
233
234 /// Create a new (empty) \c formats using the \a bases as backing \c formats. This forms a directed graph of
235 /// \c formats objects. When searching for a \c deserializer or \c serializer, the \c formats is searched, then each
236 /// base is searched depth-first left-to-right.
237 ///
238 /// \param bases Is the list of \c formats objects to use as bases for the newly-created \c formats. Order matters
239 /// -- the \a bases are searched left-to-right, so \c formats that are farther left take precedence
240 /// over those more to the right.
241 ///
242 /// \note
243 /// It is impossible to form an endless loop of \c formats objects, since the base of all \c formats are eventually
244 /// empty. If there is an existing set of nodes \f$ k \f$ and each new \c format created with \c compose is in
245 /// \f$ k+1 \f$, there is no way to create a link from \f$ k \f$ into \f$ k+1 \f$ and so there is no way to create a
246 /// circuit.
248 static formats compose(const list& bases);
249
250 /// Deserialize the provided \a type \a from a \c reader \a into an area of memory. The \a context is passed to the
251 /// \c deserializer which performs the conversion. In general, this should not be used directly as it is quite
252 /// painful to do so -- prefer \c deserialization_context::deserialize or the free function \c jsonv::deserialize.
253 ///
254 /// This only finds the \c deserializer and calls it. What \c deserialization_context::deserialize does around that
255 /// call is skipped: an exception is not recorded as a problem but propagates, and a deserialization started from a
256 /// hook which can see \c deserialization_context::source_value or \c deserialization_context::encoded_source does
257 /// not have them hidden.
258 ///
259 /// \returns whatever the located \c deserializer returned; see \c deserializer::deserialize.
260 ///
261 /// \throws no_deserializer if a \c deserializer for \a type could not be found.
263 std::expected<void, ast_node_type> deserialize(const std::type_info& type,
264 reader& from,
265 void* into,
267 ) const;
268
269 /// Get the \c deserializer for the given \a type.
270 ///
271 /// \throws no_deserializer if a \c deserializer for \a type could not be found.
273 const deserializer& get_deserializer(std::type_index type) const;
274
275 /// Get the \c deserializer for the given \a type.
276 ///
277 /// \throws no_deserializer if a \c deserializer for \a type could not be found.
279 const deserializer& get_deserializer(const std::type_info& type) const;
280
281 /// Write the object of \a type at \a from into \a to. The \a context is passed to the \c serializer which performs
282 /// the conversion. In general, this should not be used directly as it is painful to do so -- prefer
283 /// \c serialization_context::serialize or the free function \c jsonv::to_json.
284 ///
285 /// This only finds the \c serializer and calls it. What \c serialization_context::serialize does around that call
286 /// is skipped: an exception which is not a \c serialization_error propagates as it was thrown rather than being
287 /// wrapped in one.
288 ///
289 /// \throws no_serializer if a \c serializer for \a type could not be found. Its \c serialization_error::path is
290 /// \c to.current_path(), where the value was wanted.
291 void serialize(const std::type_info& type,
292 const void* from,
293 writer& to,
295 ) const;
296
297 /// Encode the provided value \a from into a JSON \c value: \ref serialize into a \c value_encoder. In general, this
298 /// should not be used directly -- prefer \c serialization_context::to_json or the free function \c jsonv::to_json.
299 ///
300 /// \throws no_serializer if a \c serializer for \a type could not be found.
301 /// \throws std::logic_error if the \c serializer wrote nothing or left a structure open, as \c value_encoder::take
302 /// does.
304 value to_json(const std::type_info& type,
305 const void* from,
307 ) const;
308
309 /// Gets the \c serializer for the given \a type.
310 ///
311 /// \throws no_serializer if a \c serializer for \a type could not be found.
313 const serializer& get_serializer(std::type_index type) const;
314
315 /// Gets the \c serializer for the given \a type.
316 ///
317 /// \throws no_serializer if a \c serializer for \a type could not be found.
319 const serializer& get_serializer(const std::type_info& type) const;
320
321 /// Register a \c deserializer that lives in some unmanaged space.
322 ///
323 /// \throws duplicate_type_error if this \c formats instance already has a \c deserializer that serves the provided
324 /// \c deserializer::get_type and the \c duplicate_type_action is \c exception.
325 void register_deserializer(const deserializer*, duplicate_type_action action = duplicate_type_action::exception);
326
327 /// Register a \c deserializer with shared ownership between this \c formats instance and anything else.
328 ///
329 /// \throws duplicate_type_error if this \c formats instance already has a \c deserializer that serves the provided
330 /// \c deserializer::get_type and the \c duplicate_type_action is \c exception.
331 void register_deserializer(std::shared_ptr<const deserializer>,
332 duplicate_type_action action = duplicate_type_action::exception);
333
334 /// Register a \c serializer that lives in some managed space.
335 ///
336 /// \throws duplicate_type_error if this \c formats instance already has a \c serializer that serves the provided
337 /// \c serializer::get_type and the \c duplicate_type_action is \c exception.
338 void register_serializer(const serializer*, duplicate_type_action action = duplicate_type_action::exception);
339
340 /// Register a \c serializer with shared ownership between this \c formats instance and anything else.
341 ///
342 /// \throws duplicate_type_error if this \c formats instance already has a \c serializer that serves the provided
343 /// \c serializer::get_type and the \c duplicate_type_action is \c exception.
344 void register_serializer(std::shared_ptr<const serializer>,
345 duplicate_type_action action = duplicate_type_action::exception);
346
347 /// Register an \c adapter that lives in some unmanaged space.
348 ///
349 /// \throws duplicate_type_error if this \c formats instance already has either a \c deserializer or \c serializer
350 /// that serves the provided \c adapter::get_type and the \c duplicate_type_action is
351 /// \c exception.
352 void register_adapter(const adapter*, duplicate_type_action action = duplicate_type_action::exception);
353
354 /// Register an \c adapter with shared ownership between this \c formats instance and anything else.
355 ///
356 /// \throws duplicate_type_error if this \c formats instance already has either a \c deserializer or \c serializer
357 /// that serves the provided \c adapter::get_type the \c duplicate_type_action is
358 /// \c exception.
359 void register_adapter(std::shared_ptr<const adapter>,
360 duplicate_type_action action = duplicate_type_action::exception);
361
362 /// Test for equality between this instance and \a other. If two \c formats are equal, they are the \e exact same
363 /// node in the graph. Even if one \c formats has the exact same types for the exact same <tt>deserializer</tt>s. A
364 /// moved-from \c formats is in no node, so it is equal only to another moved-from one.
366 bool operator==(const formats& other) const;
367
368 /// Test for inequality between this instance and \a other. The opposite of \c operator==.
370 bool operator!=(const formats& other) const;
371
372private:
373 struct data;
374
375private:
376 explicit formats(std::vector<std::shared_ptr<const data>> bases);
377
378 /// The node to register into, which a moved-from instance makes anew.
379 data& writable_data();
380
381private:
382 std::shared_ptr<data> _data;
383};
384
385/// \}
386
387}
An adapter is both a deserializer and a serializer.
Definition adapter.hpp:27
Provides extra information to routines used for deserialization and serialization.
Definition context.hpp:26
Provides extra information to routines used for deserialization, collects the problems they encounter...
A deserializer holds the method for converting JSON source into an arbitrary C++ type.
Exception thrown if an insertion of a deserializer or serializer into a formats is attempted,...
Definition formats.hpp:48
Simply put, this class is a collection of deserializer and serializer instances.
Definition formats.hpp:157
static formats global()
Get the global formats instance.
static formats set_global(formats)
Set the global formats instance.
static formats reset_global()
Reset the global formats instance to defaults.
formats(const formats &)=default
Copying a formats shares the node in the graph rather than duplicating it, so register_ calls through...
formats(formats &&) noexcept=default
Copying a formats shares the node in the graph rather than duplicating it, so register_ calls through...
static formats defaults()
Get the default formats instance.
static formats coerce()
Get the coercing formats instance.
formats()
Create a new, empty formats instance.
Thrown when formats::deserialize does not have a deserializer for the provided type.
Definition formats.hpp:64
no_deserializer(const std::type_index &type)
Create a new exception.
no_deserializer(const std::type_info &type)
Create a new exception.
Thrown when formats::serialize does not have a serializer for the type it was asked to serialize.
Definition formats.hpp:94
no_serializer(const std::type_info &type, jsonv::path path=jsonv::path())
Create a new exception for the missing type, wanted at path.
no_serializer(const std::type_index &type, jsonv::path path=jsonv::path())
Create a new exception for the missing type, wanted at path.
Represents an exact path in some JSON structure.
Definition path.hpp:107
A reader instance reads from some form of JSON source (probably a string) and converts it into a JSON...
Definition reader.hpp:106
Provides extra information to routines used for serialization: the formats to find other serializers ...
Definition serialize.hpp:42
Exception thrown if there is any problem running serialize or to_json.
A serializer holds the method for converting an arbitrary C++ type into JSON, written token by token ...
Represents a single JSON value, which can be any one of a potential kind, each behaving slightly diff...
Definition value.hpp:113
A writer instance writes a JSON ast_node sequence to some form of sink: an encoder,...
Definition writer.hpp:68
Copyright (c) 2014-2020 by Travis Gockel.
Copyright (c) 2012-2020 by Travis Gockel.
#define JSONV_NODISCARD
Warn if the caller discards the result of this function.
Definition config.hpp:143
#define JSONV_PUBLIC
This function or class is part of the public API for JSON Voorhees.
Definition config.hpp:124
duplicate_type_action
The action to take when an insertion of a deserializer or serializer into a formats is attempted,...
Definition formats.hpp:35
@ exception
A duplicate_type_error should be thrown.
@ ignore
The existing deserializer or serializer should be kept, but no exception should be thrown.
@ replace
The new deserializer or serializer should be inserted, and no exception should be thrown.
ast_node_type
Marker type for an encountered token type.
Definition ast.hpp:87
STL namespace.
Support for JSONPath.
The exception a failed serialization throws.