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
15#include <expected>
16#include <memory>
17#include <stdexcept>
18#include <string>
19#include <string_view>
20#include <typeindex>
21#include <typeinfo>
22#include <vector>
23
24namespace jsonv
25{
26
27/// \addtogroup Serialization
28/// \{
29
30/// The action to take when an insertion of an extractor or serializer into a formats is attempted, but there is alredy
31/// an extractor or serializer for that type.
32enum class duplicate_type_action : unsigned char
33{
34 /// The existing extractor or serializer should be kept, but no exception should be thrown.
35 ignore,
36 /// The new extractor or serializer should be inserted, and no exception should be thrown.
37 replace,
38 /// A \ref duplicate_type_error should be thrown.
40};
41
42/// Exception thrown if an insertion of an extractor or serializer into a formats is attempted, but there is already an
43/// extractor or serializer for that type.
45 public std::invalid_argument
46{
47public:
48 explicit duplicate_type_error(const std::string& operation, const std::type_index& type);
49
50 virtual ~duplicate_type_error() noexcept override;
51
53 const std::type_index& type_index() const { return _type_index; }
54
55private:
56 std::type_index _type_index;
57};
58
59/// Thrown when \c formats::extract does not have an \c extractor for the provided type.
61 public std::runtime_error
62{
63public:
64 /// \{
65 /// Create a new exception.
66 explicit no_extractor(const std::type_info& type);
67 explicit no_extractor(const std::type_index& type);
68 /// \}
69
70 virtual ~no_extractor() noexcept;
71
72 /// The name of the type.
74 std::string_view type_name() const;
75
76 /// Get an ID for the type of \c extractor that \c formats::extract could not locate.
78 std::type_index type_index() const;
79
81 std::type_index _type_index;
82 std::string _type_name;
83};
84
85/// Thrown when \c formats::to_json does not have a \c serializer for the provided type.
87 public std::runtime_error
88{
89public:
90 /// Create a new exception.
91 explicit no_serializer(const std::type_info& type);
92 explicit no_serializer(const std::type_index& type);
93
94 virtual ~no_serializer() noexcept;
95
96 /// The name of the type.
98 std::string_view type_name() const;
99
100 /// Get an ID for the type of \c serializer that \c formats::to_json could not locate.
102 std::type_index type_index() const;
103
104private:
105 std::type_index _type_index;
106 std::string _type_name;
107};
108
109/// Simply put, this class is a collection of \c extractor and \c serializer instances.
110///
111/// Ultimately, \c formats form a directed graph of possible types to load. This allows you to compose formats including
112/// your application with any number of 3rd party libraries an base formats. Imagine an application that has both a user
113/// facing API and an object storage system, both of which speak JSON. When loading from the database, you would like
114/// strict type enforcement -- check that when you say <tt>extract&lt;int&gt;(val)</tt>, that \c val actually has
115/// \c kind::integer. When getting values from the API, you happily convert \c kind::string with the value \c "51" into
116/// the integer \c 51. You really don't want to have to write two versions of decoders for all of your objects: one
117/// which uses the standard checked \c value::as_integer and one that uses \c coerce_integer -- you want the same object
118/// model.
119///
120/// \dot
121/// digraph formats {
122/// defaults [label="formats::defaults"]
123/// coerce [label="formats::coerce"]
124/// lib [label="some_3rd_party"]
125/// my_app [label="my_app_formats"]
126/// db [label="my_app_database_loader"]
127/// api [label="my_app_api_loader"]
128///
129/// my_app -> lib
130/// db -> defaults
131/// db -> my_app
132/// api -> coerce
133/// api -> my_app
134/// }
135/// \enddot
136///
137/// To do this, you would create your object model (called \c my_app_formats in the chart) with the object models for
138/// your application-specific types. This can use any number of 3rd party libraries to get the job done. To make a
139/// functional \c formats, you would \c compose different \c formats instances into a single one. From there,
140///
141/// \code
142/// jsonv::formats get_api_formats()
143/// {
144/// static jsonv::formats instance = jsonv::formats::compose({ jsonv::formats::coerce(), get_app_formats() });
145/// return instance;
146/// }
147///
148/// jsonv::formats get_db_formats()
149/// {
150/// static jsonv::formats instance = jsonv::formats::compose({ jsonv::formats::defaults(), get_app_formats() });
151/// return instance;
152/// }
153///
154/// MyType extract_thing(const jsonv::value& from, bool from_db)
155/// {
156/// return jsonv::extract<MyType>(from, from_db ? get_db_formats() : get_api_formats());
157/// }
158/// \endcode
160{
161public:
162 using list = std::vector<formats>;
163
164public:
165 /// Get the default \c formats instance. This uses \e strict type-checking and behaves by the same rules as the
166 /// \c value \c as_ member functions (\c as_integer, \c as_string, etc): the JSON node has to be the kind the C++
167 /// type is built from.
168 ///
169 /// Range is the one place the built-ins are stricter than those accessors. An integer literal which does not fit
170 /// the destination is reported as a problem rather than saturated to \c std::int64_t and then wrapped into it, so
171 /// \c 999 is not a \c std::uint8_t and \c -1 is not a \c std::uint64_t.
172 ///
173 /// A \c std::string_view is extracted as a view of the source rather than a copy, so the source has to outlive
174 /// it: the caller's \c value for an in-memory extraction, the JSON text for a \c reader over one. A string the
175 /// source spelt with escape sequences has no decoded form in it to view and is refused, as is one belonging to a
176 /// tree the pipeline materialised and is about to free -- see \c extraction_context::source_is_temporary.
177 ///
178 /// \note
179 /// This function actually returns a \e copy of the default \c formats, so modifications do not affect the actual
180 /// instance.
183
184 /// Get the global \c formats instance. By default, this is the same as \c defaults, but you can override it with
185 /// \c set_global. The \c extract function uses this \c formats instance if none is provided, so this is convenient
186 /// if your application only has one type of \c formats to use.
187 ///
188 /// \note
189 /// This function actually returns a \e copy of the global \c formats, so modifications do not affect the actual
190 /// instance. If you wish to alter the global formats, use \c set_global.
192 static formats global();
193
194 /// Set the \c global \c formats instance.
195 ///
196 /// \returns the previous value of the global formats instance.
198
199 /// Reset the \c global \c formats instance to \c defaults.
200 ///
201 /// \returns the previous value of the global formats instance.
203
204 /// Get the coercing \c formats instance. This uses \e loose type-checking and behaves by the same rules as the
205 /// \c coerce_ functions in \c coerce.hpp: a string holding \c "10" extracts as the integer \c 10, an integer
206 /// extracts as the string \c "5", and every kind has a truth value -- \c kind::null's is \c false. It is composed
207 /// over \c defaults, so anything it does not redefine -- \c value and \c std::string_view among them -- behaves
208 /// as it does there, range checking included.
209 ///
210 /// \note
211 /// This function actually returns a \e copy of the default \c formats, so modifications do not affect the actual
212 /// instance.
214 static formats coerce();
215
216 /// Create a new, empty \c formats instance. By default, this does not know how to extract anything -- not even the
217 /// basic types like \c int64_t or \c std::string.
219
221
222 /// Create a new (empty) \c formats using the \a bases as backing \c formats. This forms a directed graph of
223 /// \c formats objects. When searching for an \c extractor or \c serializer, the \c formats is searched, then each
224 /// base is searched depth-first left-to-right.
225 ///
226 /// \param bases Is the list of \c formats objects to use as bases for the newly-created \c formats. Order matters
227 /// -- the \a bases are searched left-to-right, so \c formats that are farther left take precedence
228 /// over those more to the right.
229 ///
230 /// \note
231 /// It is impossible to form an endless loop of \c formats objects, since the base of all \c formats are eventually
232 /// empty. If there is an existing set of nodes \f$ k \f$ and each new \c format created with \c compose is in
233 /// \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
234 /// circuit.
236 static formats compose(const list& bases);
237
238 /// Extract the provided \a type \a from a \c reader \a into an area of memory. The \a context is passed to the
239 /// \c extractor which performs the conversion. In general, this should not be used directly as it is quite painful
240 /// to do so -- prefer \c extraction_context::extract or the free function \c jsonv::extract.
241 ///
242 /// \returns whatever the located \c extractor returned; see \c extractor::extract.
243 ///
244 /// \throws no_extractor if an \c extractor for \a type could not be found.
246 std::expected<void, ast_node_type> extract(const std::type_info& type,
247 reader& from,
248 void* into,
250 ) const;
251
252 /// Get the \c extractor for the given \a type.
253 ///
254 /// \throws no_extractor if an \c extractor for \a type could not be found.
256 const extractor& get_extractor(std::type_index type) const;
257
258 /// Get the \c extractor for the given \a type.
259 ///
260 /// \throws no_extractor if an \c extractor for \a type could not be found.
262 const extractor& get_extractor(const std::type_info& type) const;
263
264 /// Encode the provided value \a from into a JSON \c value. The \a context is passed to the \c serializer which
265 /// performs the conversion. In general, this should not be used directly as it is painful to do so -- prefer
266 /// \c serialization_context::to_json or the free function \c jsonv::to_json.
267 ///
268 /// \throws no_serializer if a \c serializer for \a type could not be found.
270 value to_json(const std::type_info& type,
271 const void* from,
273 ) const;
274
275 /// Gets the \c serializer for the given \a type.
276 ///
277 /// \throws no_serializer if a \c serializer for \a type could not be found.
279 const serializer& get_serializer(std::type_index type) const;
280
281 /// Gets the \c serializer for the given \a type.
282 ///
283 /// \throws no_serializer if a \c serializer for \a type could not be found.
285 const serializer& get_serializer(const std::type_info& type) const;
286
287 /// Register an \c extractor that lives in some unmanaged space.
288 ///
289 /// \throws duplicate_type_error if this \c formats instance already has an \c extractor that serves the provided
290 /// \c extractor::get_type and the \c duplicate_type_action is \c exception.
291 void register_extractor(const extractor*, duplicate_type_action action = duplicate_type_action::exception);
292
293 /// Register an \c extractor with shared ownership between this \c formats instance and anything else.
294 ///
295 /// \throws duplicate_type_error if this \c formats instance already has an \c extractor that serves the provided
296 /// \c extractor::get_type and the \c duplicate_type_action is \c exception.
297 void register_extractor(std::shared_ptr<const extractor>,
299
300 /// Register a \c serializer that lives in some managed space.
301 ///
302 /// \throws duplicate_type_error if this \c formats instance already has a \c serializer that serves the provided
303 /// \c serializer::get_type and the \c duplicate_type_action is \c exception.
304 void register_serializer(const serializer*, duplicate_type_action action = duplicate_type_action::exception);
305
306 /// Register a \c serializer with shared ownership between this \c formats instance and anything else.
307 ///
308 /// \throws duplicate_type_error if this \c formats instance already has a \c serializer that serves the provided
309 /// \c serializer::get_type and the \c duplicate_type_action is \c exception.
310 void register_serializer(std::shared_ptr<const serializer>,
312
313 /// Register an \c adapter that lives in some unmanaged space.
314 ///
315 /// \throws duplicate_type_error if this \c formats instance already has either an \c extractor or \c serializer
316 /// that serves the provided \c adapter::get_type and the \c duplicate_type_action is
317 /// \c exception.
318 void register_adapter(const adapter*, duplicate_type_action action = duplicate_type_action::exception);
319
320 /// Register an \c adapter with shared ownership between this \c formats instance and anything else.
321 ///
322 /// \throws duplicate_type_error if this \c formats instance already has either an \c extractor or \c serializer
323 /// that serves the provided \c adapter::get_type the \c duplicate_type_action is
324 /// \c exception.
325 void register_adapter(std::shared_ptr<const adapter>,
327
328 /// Test for equality between this instance and \a other. If two \c formats are equal, they are the \e exact same
329 /// node in the graph. Even if one \c formats has the exact same types for the exact same <tt>extractor</tt>s.
332
333 /// Test for inequality between this instance and \a other. The opposite of \c operator==.
336
337private:
338 struct data;
339
340private:
341 explicit formats(std::vector<std::shared_ptr<const data>> bases);
342
343private:
344 std::shared_ptr<data> _data;
345};
346
347/// \}
348
349}
An adapter is both an extractor and a serializer.
Definition adapter.hpp:27
Provides extra information to routines used for extraction and serialization.
Definition context.hpp:26
Exception thrown if an insertion of an extractor or serializer into a formats is attempted,...
Definition formats.hpp:46
An adapter for enumeration types.
Provides extra information to routines used for extraction, collects the problems they encounter,...
Definition extract.hpp:313
An extractor holds the method for converting JSON source into an arbitrary C++ type.
Definition extract.hpp:272
Simply put, this class is a collection of extractor and serializer instances.
Definition formats.hpp:160
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.
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::extract does not have an extractor for the provided type.
Definition formats.hpp:62
no_extractor(const std::type_info &type)
Thrown when formats::to_json does not have a serializer for the provided type.
Definition formats.hpp:88
no_serializer(const std::type_info &type)
Create a new exception.
A reader instance reads from some form of JSON source (probably a string) and converts it into a JSON...
Definition reader.hpp:96
A serializer holds the method for converting an arbitrary C++ type into a value.
Represents a single JSON value, which can be any one of a potential kind, each behaving slightly diff...
Definition value.hpp:113
Copyright (c) 2014-2020 by Travis Gockel.
#define JSONV_NODISCARD
Warn if the caller discards the result of this function.
Definition config.hpp:121
#define JSONV_PUBLIC
This function or class is part of the public API for JSON Voorhees.
Definition config.hpp:102
Copyright (c) 2012-2020 by Travis Gockel.
duplicate_type_action
The action to take when an insertion of an extractor or serializer into a formats is attempted,...
Definition formats.hpp:33
@ exception
A duplicate_type_error should be thrown.
@ ignore
The existing extractor or serializer should be kept, but no exception should be thrown.
@ replace
The new extractor 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.