JSON Voorhees
Killer JSON for C++
Loading...
Searching...
No Matches
reader.hpp
Go to the documentation of this file.
1/// \file jsonv/reader.hpp
2/// Read a JSON AST.
3///
4/// Copyright (c) 2015-2022 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/ast.hpp>
15#include <jsonv/optional.hpp>
16#include <expected>
17#include <string_view>
18
19#include <cstdint>
20#include <initializer_list>
21#include <memory>
22#include <string>
23
24namespace jsonv
25{
26
27class parse_index;
28class parse_options;
29class path;
30class value;
31
32namespace detail
33{
34
35class reader_lookahead;
36
37}
38
39/// \addtogroup Serialization
40/// \{
41
42/// A reader instance reads from some form of JSON source (probably a string) and converts it into a JSON \ref ast_node
43/// sequence.
44///
45/// Readers normalize access to JSON source for conversion to some other format. They can be provided with pre-parsed
46/// JSON through a \c parse_index or \c value. They can be provided with a \c std::string or \c std::string_view directly.
47/// This allows \c extractor implementations to operate on all forms of JSON without worrying about the implementation.
48///
49/// A reader is a forward cursor over that sequence. It starts on \c ast_node_type::document_start, so the first thing
50/// to do is step onto the value itself -- which \c jsonv::extract does for you when handed a fresh reader. Reading an
51/// object means walking its keys, handling the ones you recognize and skipping the ones you do not:
52///
53/// \code
54/// struct my_object
55/// {
56/// std::int64_t a = 0;
57/// };
58///
59/// std::optional<my_object> extract_my_object(jsonv::reader& from)
60/// {
61/// if (!from.expect(jsonv::ast_node_type::object_begin))
62/// return std::nullopt;
63///
64/// // Step onto the first key, or onto the } of an empty object.
65/// if (!from.next_token())
66/// return std::nullopt;
67///
68/// my_object out;
69/// while (from.good() && from.current_type() != jsonv::ast_node_type::object_end)
70/// {
71/// // Keys arrive canonical or escaped, depending on whether the source used escape sequences.
72/// if (!from.expect({ jsonv::ast_node_type::key_canonical, jsonv::ast_node_type::key_escaped }))
73/// return std::nullopt;
74///
75/// auto key = from.current().visit_key([](const auto& k) { return std::string(k.value()); });
76/// if (key == "a")
77/// {
78/// if (!from.next_token())
79/// return std::nullopt;
80///
81/// if (auto node = from.current_as<jsonv::ast_node::integer>())
82/// out.a = node->value();
83/// else
84/// return std::nullopt;
85///
86/// // Step off the value and onto the next key, or onto the closing }.
87/// if (!from.next_token())
88/// return std::nullopt;
89/// }
90/// else
91/// {
92/// // A key we do not care about -- skip its value, however large, and land on the next key.
93/// if (!from.next_key())
94/// return std::nullopt;
95/// }
96/// }
97/// return out;
98/// }
99/// \endcode
100///
101/// Note that \c next_key is only valid while sitting on a key or on the opening \c { -- it is the "skip this member"
102/// step, not the loop's advance. Use \c next_token to move off a value you have just read.
104{
105public:
106 /// Create a reader which reads from the given \a index.
107 explicit reader(parse_index index);
108
109 /// \{
110
111 /// Create a reader which reads from an in-memory \a value.
112 ///
113 /// \param value The value to read from. The overload taking a reference does not copy it, so it must remain
114 /// valid for the lifetime of the reader; the rvalue overload moves \a value into the reader, which
115 /// then keeps it alive.
116 ///
117 /// These are named rather than constructors because \c value converts implicitly from \c std::string, among
118 /// others. A \c reader(const value&) constructor would make <tt>reader(some_std_string)</tt> ambiguous -- a
119 /// \c std::string reaches \c std::string_view and \c value through user-defined conversions of equal rank --
120 /// and the same trap would reopen for every type \c value grows a converting constructor from. Returning by
121 /// value costs nothing: the result is a prvalue of the returned type, so it initializes the caller's object
122 /// directly without a move.
124 static reader from_value(const value& value);
127 /// \}
128
129 /// \{
130
131 /// Create a reader which reads from JSON \a source.
132 ///
133 /// The \a source must stay in memory for the duration of this instance's use, unless it is an rvalue reference to
134 /// a \c std::string, which is moved to the reader's implementation to keep it alive. It is parsed with
135 /// \a parse_options where they are given, and with the defaults \c parse_index::parse uses where they are not.
136 explicit reader(std::string_view source);
137 explicit reader(std::string_view source, const parse_options& parse_options);
138 explicit reader(const char* source);
139 explicit reader(const char* source, const parse_options& parse_options);
140
141 explicit reader(std::string&& source);
142 explicit reader(std::string&& source, const parse_options& parse_options);
143 /// \}
144
145 // Not copyable.
146 reader(const reader&) = delete;
147 reader& operator=(const reader&) = delete;
148
149 /// \{
150
151 /// Moving a reader transfers its implementation, leaving the source moved-from: \c good is \c false and the
152 /// accessors throw \c std::invalid_argument. These are out-of-line because destroying the implementation needs a
153 /// complete \c reader::impl, which this header does not have -- the same reason the destructor is.
154 reader(reader&&) noexcept;
155 reader& operator=(reader&&) noexcept;
156 /// \}
157
158 ~reader() noexcept;
159
160 /// Check if this reader is still good to read from. This will be \c true if this instance has not been moved-from
161 /// and has not reached EOF. If this is \c false, \c current or \c current_path will throw an exception.
163 bool good() const;
164
165 /// Check that the source this reader was created from is valid JSON.
166 ///
167 /// Parsing text never throws: a malformed document produces a sequence which stops at an \c ast_node_type::error
168 /// node, and a reader walks it as far as it goes. What was wrong is not on that node in any form a reader of it can
169 /// act on, so this is the question \c parse_index::validate answers, asked of the reader. It is about the source,
170 /// not the cursor, so the answer is the same wherever the reader is positioned.
171 ///
172 /// \throws parse_error if this reader is over JSON text which did not parse. A reader over a \c value has nothing
173 /// to parse and never throws this.
174 /// \throws std::invalid_argument if this instance has been moved-from.
175 void validate() const;
176
177 /// Does this reader own the storage it reads from?
178 ///
179 /// This is \c true for a reader created from a \c std::string rvalue or by \c from_value(value&&), which keep their
180 /// source alive for exactly as long as the reader. Anything extracted as a view of the source -- a
181 /// \c std::string_view, say -- is then valid only while the reader is. It is \c false for every other source,
182 /// where the caller owns the storage, and for a moved-from reader.
184 bool owns_source() const noexcept;
185
186 /// Get the current AST node this reader is pointing at.
187 ///
188 /// \throws std::logic_error if this instance is not \c good, or std::invalid_argument if it has been moved-from.
190 const ast_node& current() const;
191
192 /// Get the type of the \c current AST node, which is always \c current().type().
193 ///
194 /// Use this where the type is all that is wanted -- checking for the `]` which ends an array, say. A reader over a
195 /// \c value has to synthesise token text for a number, a string or a key before it can hand out an \c ast_node,
196 /// and answering this does not.
197 ///
198 /// \throws std::logic_error if this instance is not \c good, or std::invalid_argument if it has been moved-from.
200 ast_node_type current_type() const;
201
202 /// \{
203
204 /// Check that the \c current AST node has the given \a type or is one of the expected \a types.
205 ///
206 /// \returns Nothing if the \c current node matches \a type or one of the given \a types; otherwise the
207 /// \c ast_node_type the \c current node actually has.
208 /// \throws std::invalid_argument if \a types is empty.
209 /// \throws std::logic_error if this instance is not \c good, or std::invalid_argument if it has been moved-from.
210 ///
211 /// \see ast_node::expect
213 std::expected<void, ast_node_type> expect(ast_node_type type) const;
215 std::expected<void, ast_node_type> expect(std::initializer_list<ast_node_type> types) const;
216 /// \}
217
218 /// Get the \c current AST node as a specific \c TAstNode subtype, calling \c expect beforehand.
219 ///
220 /// \returns The \c current node as a \c TAstNode; otherwise the \c ast_node_type the \c current node actually has.
221 /// \throws std::logic_error if this instance is not \c good, or std::invalid_argument if it has been moved-from.
222 template <typename TAstNode>
224 std::expected<TAstNode, ast_node_type> current_as() const
225 {
226 // Written as an explicit branch rather than `expect(...).transform(...)` on purpose. The monadic operations on
227 // `std::expected` are a later addition than the type itself -- libstdc++ 12 and libc++ 16 have `expected` but
228 // no `transform` -- so using one here would quietly raise the minimum toolchain by a whole release.
229 if (auto matched = expect(TAstNode::type()); !matched)
230 return std::unexpected(matched.error());
231 else
232 return current().as<TAstNode>();
233 }
234
235 /// Get the in-memory \c value this reader is positioned on, if it has one to lend.
236 ///
237 /// A reader created by \c from_value is walking a \c value which already exists, so the subtree under \c current
238 /// is something it can hand out by reference rather than rebuild. A reader over JSON text has no such tree.
239 ///
240 /// Extraction uses this to avoid copying a subtree it was already given, and so that an extractor which returns a
241 /// view of what it was handed -- \c std::string_view among them -- borrows the caller's storage rather than a
242 /// temporary which dies with the call.
243 ///
244 /// \returns The value \c current names; or nothing if this reader is not value-backed, is not \c good, or is
245 /// positioned somewhere which does not start a value, such as an object key or a closing token.
248
249 /// Get the path to the current node this reader is pointing at. This is used in the generation of error messages to
250 /// describe the location of something that could not be extracted.
251 ///
252 /// \code
253 /// ^ /* "." -- start of document is the empty path */
254 /// { /* "." -- opening { is still an empty path */
255 /// "a": /* ".a" -- the key starts the path */
256 /// [ /* ".a" -- the path refers to the entire array */
257 /// 1, /* ".a[0]" */
258 /// 2, /* ".a[1]" */
259 /// 3, /* ".a[2]" */
260 /// ], /* ".a" -- the path at the end of the array refers to the entire array again */
261 /// "b": /* ".b" */
262 /// { /* ".b" -- the path refers to the entire object */
263 /// "x": /* ".b.x" */
264 /// "taco" /* ".b.x" */
265 /// }, /* ".b" */
266 /// "c": /* ".c" */
267 /// 4 /* ".c" */
268 /// } /* "." */
269 /// $ /* "." */
270 /// \endcode
271 ///
272 /// \throws std::logic_error if this instance is not \c good, or std::invalid_argument if it has been moved-from.
274 const path& current_path() const;
275
276 /// Go to the next token.
277 ///
278 /// \code
279 /// ^
280 /// { /* <- go to "a" */
281 /// "a": /* <- go to [ */
282 /// [ /* <- go to 1 */
283 /// 1, /* <- go to 2 */
284 /// 2, /* ...and so on */
285 /// 3,
286 /// ],
287 /// "b":
288 /// {
289 /// },
290 /// "c":
291 /// 4
292 /// }
293 /// $
294 /// \endcode
295 ///
296 /// \returns \c true if the reader is still \c good to read from \c current.
298 bool next_token() noexcept;
299
300 /// Go to one past the end of the current structure.
301 ///
302 /// \code
303 /// ^
304 /// {
305 /// "a": /* <- go to end of document */
306 /// [ /* <- go to "b" */
307 /// 1, /* <- go to "b", too */
308 /// 2,
309 /// 3,
310 /// ], /* <- go to "b" */
311 /// "b": /* <- go to end of document */
312 /// { /* <- go to "c" */
313 /// }, /* <- go to "c" */
314 /// "c":
315 /// 4
316 /// } /* <- go to end of document */
317 /// $
318 /// \endcode
319 ///
320 /// The point of this over \ref next_token is abandoning a structure you are only part-way through. Once the one
321 /// member you came for has been read, there is no reason to walk the rest of the object:
322 ///
323 /// \code
324 /// /// Find the "a" member of an object and leave the rest of it unread.
325 /// std::optional<std::int64_t> find_a(jsonv::reader& from)
326 /// {
327 /// if (!from.expect(jsonv::ast_node_type::object_begin))
328 /// return std::nullopt;
329 ///
330 /// if (!from.next_token())
331 /// return std::nullopt;
332 ///
333 /// while (from.good() && from.current_type() != jsonv::ast_node_type::object_end)
334 /// {
335 /// if (!from.expect({ jsonv::ast_node_type::key_canonical, jsonv::ast_node_type::key_escaped }))
336 /// return std::nullopt;
337 ///
338 /// auto key = from.current().visit_key([](const auto& k) { return std::string(k.value()); });
339 /// if (key != "a")
340 /// {
341 /// if (!from.next_key())
342 /// return std::nullopt;
343 ///
344 /// continue;
345 /// }
346 ///
347 /// if (!from.next_token())
348 /// return std::nullopt;
349 ///
350 /// auto node = from.current_as<jsonv::ast_node::integer>();
351 ///
352 /// // Whatever is left of this object, we are done with it.
353 /// (void) from.next_structure();
354 ///
355 /// if (node)
356 /// return node->value();
357 /// else
358 /// return std::nullopt;
359 /// }
360 /// return std::nullopt;
361 /// }
362 /// \endcode
363 ///
364 /// Note the call site: \c next_structure is used while sitting on a *value* inside the object, which is where it
365 /// differs from \ref next_token. Called on the closing \c } itself it is merely \ref next_token, since there is no
366 /// longer a structure to leave.
367 ///
368 /// \returns \c true if the reader is still \c good to read from \c current.
370 bool next_structure() noexcept;
371
372 /// Go to one past the value this reader is on.
373 ///
374 /// Unlike \ref next_structure, which leaves the structure the reader is *inside*, this steps over the single value
375 /// the reader is *on*. On a structure that means the token *after* its matching close, since the structure is the
376 /// value being stepped over; on anything else it is the same as \ref next_token.
377 ///
378 /// \code
379 /// ^
380 /// {
381 /// "a": /* <- go to [ */
382 /// [ /* <- go to "b" */
383 /// 1, /* <- go to 2 */
384 /// 2,
385 /// 3,
386 /// ],
387 /// "b":
388 /// { /* <- go to "c" */
389 /// },
390 /// "c":
391 /// 4 /* <- go to } */
392 /// }
393 /// $
394 /// \endcode
395 ///
396 /// This is the primitive for ignoring a value you do not want. Note the difference from \ref next_structure at the
397 /// `4` above: this goes to the `}`, while \ref next_structure leaves the enclosing object entirely.
398 ///
399 /// \returns \c true if the reader is still \c good to read from \c current.
401 bool next_value() noexcept;
402
403 /// Go to the next object key or end-of-object.
404 ///
405 /// \code
406 /// ^
407 /// {
408 /// "a": /* <- calling here goes to "b" */
409 /// [ /* <- calling when not on an object key throws */
410 /// 1,
411 /// 2,
412 /// 3,
413 /// ],
414 /// "b": /* <- calling here goes to "c" */
415 /// {
416 /// },
417 /// "c": /* <- calling here goes to end of object */
418 /// 4
419 /// }
420 /// $
421 /// \endcode
422 ///
423 /// \returns \c true if the reader is still \c good to read from \c current.
424 /// \throws std::invalid_argument if the reader is not currently at the start of a key.
426 bool next_key();
427
428private:
429 class impl;
430 class impl_parse_index;
431 class impl_parse_index_owning;
432 class impl_value;
433 class impl_value_owning;
434
435 template <typename TImpl, typename... TArgs>
436 explicit reader(std::in_place_type_t<TImpl>, TArgs&&...);
437
438 explicit reader(std::unique_ptr<impl> impl) noexcept;
439
440 /// The library sometimes has to read ahead -- \c polymorphic_adapter has to find its discriminator before it knows
441 /// which type to extract. That is a second cursor on this reader's source rather than a rewind of this one, and it
442 /// is kept out of the public interface on purpose: promising it would commit every future source, including one
443 /// which streams, to supporting it.
444 friend class detail::reader_lookahead;
445
446private:
447 std::unique_ptr<impl> _impl;
448};
449
450/// \}
451
452}
Utilities for directly dealing with a JSON AST.
Represents an entry in a JSON AST.
Definition ast.hpp:170
Represents the index of a parsed AST.
Configuration for various parsing options.
Definition parse.hpp:65
Represents an exact path in some JSON structure.
Definition path.hpp:87
A reader instance reads from some form of JSON source (probably a string) and converts it into a JSON...
Definition reader.hpp:104
static reader from_value(const value &value)
Create a reader which reads from an in-memory value.
reader(std::string_view source, const parse_options &parse_options)
Create a reader which reads from JSON source.
reader(std::string &&source, const parse_options &parse_options)
Create a reader which reads from JSON source.
reader(const char *source, const parse_options &parse_options)
Create a reader which reads from JSON source.
reader(parse_index index)
Create a reader which reads from the given index.
reader(std::string &&source)
Create a reader which reads from JSON source.
reader(std::string_view source)
Create a reader which reads from JSON source.
static reader from_value(value &&value)
Create a reader which reads from an in-memory value.
reader(const char *source)
Create a reader which reads from JSON source.
optional< const value & > current_value() const noexcept
Get the in-memory value this reader is positioned on, if it has one to lend.
reader(reader &&) noexcept
Moving a reader transfers its implementation, leaving the source moved-from: good is false and the ac...
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:132
#define JSONV_PUBLIC
This function or class is part of the public API for JSON Voorhees.
Definition config.hpp:113
ast_node_type
Marker type for an encountered token type.
Definition ast.hpp:87
STL namespace.
Pulls in an implementation of optional.
typename detail::optional_type< T >::type optional
Represents a value that may or may not be present.
Definition optional.hpp:239