JSON Voorhees
Killer JSON for C++
Loading...
Searching...
No Matches
serialize.hpp
Go to the documentation of this file.
1/// \file jsonv/serialization/serialize.hpp
2/// Serialization of C++ types into a JSON token stream.
3///
4/// Copyright (c) 2015-2026 by Travis Gockel. All rights reserved.
5///
6/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License
7/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later
8/// version.
9///
10/// \author Travis Gockel (travis@gockelhut.com)
11#pragma once
12
13#include <jsonv/config.hpp>
14#include <jsonv/forward.hpp>
15#include <jsonv/path.hpp>
18#include <jsonv/value.hpp>
19#include <jsonv/version.hpp>
20#include <jsonv/writer.hpp>
21
22#include <concepts>
23#include <iosfwd>
24#include <optional>
25#include <string>
26#include <typeinfo>
27
28namespace jsonv
29{
30
31/// \addtogroup Serialization
32/// \{
33
34/// Provides extra information to routines used for serialization: the \c formats to find other serializers in, and
35/// the \c version and user data the caller asked for.
36///
37/// Unlike a \c deserialization_context, this is immutable, and one instance can serve any number of serializations at
38/// once. Nothing is recorded on it: a serializer's position in the document is its \c writer::current_path, and a
39/// failure is reported by throwing a \c serialization_error.
41 public context
42{
43public:
44 /// Create a new instance using the default \c formats (\c formats::global).
46
47 /// Create a new instance using the given \a fmt, \a ver and \a userdata.
49 std::optional<jsonv::version> ver = std::nullopt,
50 const void* userdata = nullptr
51 );
52
53 virtual ~serialization_context() noexcept;
54
55 /// Write \a from into \a to using the \c formats associated with this context.
56 ///
57 /// This is the positioned primitive a composite calls for each of its parts: it writes one value where the writer
58 /// is -- at the root, as the next element of an open array, or as the value of the key just written -- and nothing
59 /// else.
60 ///
61 /// \throws serialization_error if the \c serializer for \c T cannot be found or throws. The writer may have been
62 /// given a prefix of the value by then.
63 template <typename T>
64 void serialize(const T& from, writer& to) const
65 {
66 serialize(typeid(T), static_cast<const void*>(&from), to);
67 }
68
69 /// Write the object of the given \a type at \a from into \a to using the \c formats associated with this context.
70 /// This is what the overload above calls with the \c T it was asked for.
71 ///
72 /// A \c serialization_error thrown by the \c serializer -- a \c no_serializer included -- is rethrown as it is,
73 /// since it already says where it is and what was being serialized there. Anything else thrown is wrapped once in a
74 /// \c serialization_error at \c to.current_path() naming \a type, with the original exception as its
75 /// \c serialization_error::nested_ptr.
76 ///
77 /// \throws serialization_error as above. The writer may have been given a prefix of the value by then: an entry
78 /// point which owns its sink discards it, and one writing into a caller's writer leaves
79 /// what was written where it is.
80 void serialize(const std::type_info& type, const void* from, writer& to) const;
81
82 /// Convenience function for converting a C++ object into a JSON value.
83 ///
84 /// This runs the same pipeline as \c serialize, writing into a \c value_encoder and handing out what it built. It
85 /// is how a serializer written against the older \c value -based interface serializes its parts, and what the free
86 /// \c jsonv::to_json runs.
87 ///
88 /// \throws serialization_error as \c serialize does, and also when the serializer wrote nothing or left a
89 /// structure open, since a JSON document is never empty.
90 template <typename T>
92 value to_json(const T& from) const
93 {
94 return to_json(typeid(T), static_cast<const void*>(&from));
95 }
96
97 /// Dynamically convert a type into a JSON value. This is what the overload above calls with the \c T it was asked
98 /// for.
100 value to_json(const std::type_info& type, const void* from) const;
101};
102
103namespace detail
104{
105
106/// Call \a func as a serialization function, writing what it produces into \a to.
107///
108/// Four call shapes are accepted, tried in this order: <tt>(context, from, writer)</tt>, <tt>(from, writer)</tt>,
109/// <tt>(context, from)</tt> and <tt>(from)</tt>. The first two write into the writer themselves. The last two are the
110/// interface functions were written against before serialization ran through a \c writer: they return a \c value, or
111/// anything one can be built from, which is written whole.
112///
113/// Each shape is tested with \c std::invocable, which asks whether the call is well-formed and nothing more. A generic
114/// lambda is invocable with any arguments its parameter list admits, so one taking <tt>(const auto&, const auto&)</tt>
115/// is tried as <tt>(from, writer)</tt> first and fails outright if its body only makes sense for a context, as the
116/// shapes of \c invoke_deserialize do. Name the parameter types.
117template <typename T, typename FSerialize>
118void invoke_serialize(const FSerialize& func, const serialization_context& context, const T& from, writer& to)
119{
120 if constexpr (std::invocable<const FSerialize&, const serialization_context&, const T&, writer&>)
121 {
122 func(context, from, to);
123 }
124 else if constexpr (std::invocable<const FSerialize&, const T&, writer&>)
125 {
126 func(from, to);
127 }
128 else if constexpr (std::invocable<const FSerialize&, const serialization_context&, const T&>)
129 {
130 to.write(func(context, from));
131 }
132 else
133 {
134 static_assert(std::invocable<const FSerialize&, const T&>,
135 "A serialization function must be callable as (const serialization_context&, const T&, writer&), "
136 "(const T&, writer&), (const serialization_context&, const T&) or (const T&)"
137 );
138
139 to.write(func(from));
140 }
141}
142
143/// The one place a public entry point serializes a whole document as text: every \c jsonv::serialize overload which
144/// writes into a \c std::ostream or returns a \c std::string comes through here.
145///
146/// The object of the given \a type at \a from is written into \a to through a \c writer of its own, over a compact
147/// \c ostream_encoder with \c ostream_encoder::ensure_ascii on, as <tt>writer(std::ostream&)</tt> makes. Once the
148/// serializer returns, the writer must hold exactly what a document is: one value, with every structure in it closed.
149///
150/// \throws serialization_error as \c serialization_context::serialize does; and also when the serializer wrote nothing,
151/// left a structure open or wrote a second value after the first, at
152/// \c writer::current_path naming \a type, with a \c std::logic_error as its
153/// \c serialization_error::nested_ptr. \c serialization_context::to_json throws the same for
154/// the first two; it keeps the second of two values, where text would run them together.
155/// Whatever was written before the failure stays in \a to.
156JSONV_PUBLIC void serialize_document(const serialization_context& context,
157 const std::type_info& type,
158 const void* from,
159 std::ostream& to
160 );
161
162/// \ref serialize_document into a string of its own, which is handed out. A failure discards what was written.
163JSONV_NODISCARD JSONV_PUBLIC std::string serialize_to_string(const serialization_context& context,
164 const std::type_info& type,
165 const void* from
166 );
167
168}
169
170/// Encode a JSON \c value from \a from using the provided \a fmts.
171template <typename T>
173value to_json(const T& from, const formats& fmts)
174{
176 return context.to_json(from);
177}
178
179/// Encode a JSON \c value from \a from using \c jsonv::formats::global().
180template <typename T>
182value to_json(const T& from)
183{
185 return context.to_json(from);
186}
187
188/// Serialize \a from into JSON text using \a fmts (by default \c jsonv::formats::global()).
189///
190/// The text is one whole document, written compactly with \c ostream_encoder::ensure_ascii on, as \c to_string writes
191/// a \c value. It is the text of <tt>to_string(to_json(from, fmts))</tt> with one difference: an object's members are
192/// in the order its serializer wrote them -- declaration order, for a type described with the serialization builder
193/// DSL -- where a \c value keeps them sorted by key. Nothing is built in between, so a container of a million elements
194/// is written without a million-node tree. To pretty-print, or to write well-formed UTF-8 as it is, construct the
195/// \c encoder yourself and serialize into a \c writer over it.
196///
197/// \a from is serialized as the type it is, as for \c to_json: a string literal is an array of \c char, which has no
198/// serializer, so pass a \c std::string_view.
199///
200/// \throws serialization_error if a serializer could not be found or failed, as \c serialization_context::serialize
201/// throws it; or if the serializer for \c T wrote anything but one whole value -- nothing,
202/// a structure left open, or a second value after the first -- since a JSON document is
203/// exactly one value.
204template <typename T>
206std::string serialize(const T& from, const formats& fmts = formats::global())
207{
208 return detail::serialize_to_string(serialization_context(fmts), typeid(T), static_cast<const void*>(&from));
209}
210
211/// Serialize \a from into JSON text through a \a context the caller built, exactly as the overload above does with one
212/// built from \c formats.
213///
214/// Everything \a context was created with applies: its \c formats, and also what the overload above has no way to be
215/// given -- the version and user data its serializers see. The serialization builder DSL's \c since and \c until ask
216/// for that version. A \c serialization_context holds nothing about any one serialization, so one may serve any
217/// number of them.
218///
219/// \throws serialization_error for the same reasons as the overload above.
220template <typename T>
222std::string serialize(const T& from, const serialization_context& context)
223{
224 return detail::serialize_to_string(context, typeid(T), static_cast<const void*>(&from));
225}
226
227/// Serialize \a from into \a to as JSON text using \a fmts (by default \c jsonv::formats::global()).
228///
229/// What is written is exactly the text the overload returning a \c std::string returns: one whole document, with
230/// nothing before or after it. Nothing separates it from a document written into the same stream next, so write a
231/// separator yourself if the stream is to be read back.
232///
233/// \throws serialization_error for the same reasons as the overload returning a \c std::string. What was written
234/// before the failure stays in \a to.
235template <typename T>
236void serialize(const T& from, std::ostream& to, const formats& fmts = formats::global())
237{
238 detail::serialize_document(serialization_context(fmts), typeid(T), static_cast<const void*>(&from), to);
239}
240
241/// Serialize \a from into \a to as JSON text through a \a context the caller built, exactly as the overload above does
242/// with one built from \c formats. Everything \a context was created with applies, as for the overload returning a
243/// \c std::string which takes one.
244///
245/// \throws serialization_error for the same reasons as the overload above. What was written before the failure stays
246/// in \a to.
247template <typename T>
248void serialize(const T& from, std::ostream& to, const serialization_context& context)
249{
250 detail::serialize_document(context, typeid(T), static_cast<const void*>(&from), to);
251}
252
253/// Serialize \a from into \a to using \a fmts (by default \c jsonv::formats::global()), as one value where the writer
254/// is: at the root, as the next element of an open array, or as the value of the key just written.
255///
256/// This is the mirror of deserializing from a \c reader the caller has positioned. What surrounds the value is the
257/// caller's, so open an array once and serialize a million items into it. Unlike the overloads which own their writer,
258/// this does not check that \a to ends up holding a whole document.
259///
260/// \throws serialization_error as \c serialization_context::serialize throws it. What was written before the failure
261/// stays in \a to.
262template <typename T>
263void serialize(const T& from, writer& to, const formats& fmts = formats::global())
264{
266 context.serialize(from, to);
267}
268
269/// Serialize \a from into \a to through a \a context the caller built, as one value where the writer is, exactly as the
270/// overload above does with one built from \c formats. This is <tt>context.serialize(from, to)</tt>, which is the
271/// spelling a \c serializer uses for its parts.
272///
273/// \throws serialization_error as \c serialization_context::serialize throws it. What was written before the failure
274/// stays in \a to.
275template <typename T>
276void serialize(const T& from, writer& to, const serialization_context& context)
277{
278 context.serialize(from, to);
279}
280
281/// \}
282
283}
Provides extra information to routines used for deserialization and serialization.
Definition context.hpp:26
Simply put, this class is a collection of deserializer and serializer instances.
Definition formats.hpp:157
static formats global()
Get the global formats instance.
Provides extra information to routines used for serialization: the formats to find other serializers ...
Definition serialize.hpp:42
value to_json(const std::type_info &type, const void *from) const
Dynamically convert a type into a JSON value.
serialization_context()
Create a new instance using the default formats (formats::global).
serialization_context(jsonv::formats fmt, std::optional< jsonv::version > ver=std::nullopt, const void *userdata=nullptr)
Create a new instance using the given fmt, ver and userdata.
void serialize(const std::type_info &type, const void *from, writer &to) const
Write the object of the given type at from into to using the formats associated with this context.
value to_json(const T &from) const
Convenience function for converting a C++ object into a JSON value.
Definition serialize.hpp:92
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
writer & write(const value &source)
Write the whole of source as one value, as encoder::encode does.
Copyright (c) 2014-2020 by Travis Gockel.
Copyright (c) 2015-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:132
#define JSONV_PUBLIC
This function or class is part of the public API for JSON Voorhees.
Definition config.hpp:113
std::string serialize(const T &from, const formats &fmts=formats::global())
Serialize from into JSON text using fmts (by default jsonv::formats::global()).
value to_json(const T &from, const formats &fmts)
Encode a JSON value from from using the provided fmts.
Support for JSONPath.
The exception a failed serialization throws.
Copyright (c) 2012-2020 by Travis Gockel.
Write a JSON AST.