JSON Voorhees
Killer JSON for C++
Loading...
Searching...
No Matches
extract.hpp
Go to the documentation of this file.
1/// \file jsonv/serialization/extract.hpp
2/// Extraction of C++ types from a JSON AST.
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/ast.hpp>
16#include <jsonv/path.hpp>
17#include <jsonv/reader.hpp>
19#include <jsonv/value.hpp>
20
21#include <concepts>
22#include <cstddef>
23#include <exception>
24#include <expected>
25#include <initializer_list>
26#include <memory>
27#include <new>
28#include <optional>
29#include <string>
30#include <string_view>
31#include <type_traits>
32#include <typeinfo>
33#include <utility>
34#include <variant>
35#include <vector>
36
37namespace jsonv
38{
39
40namespace detail
41{
42
43class borrowed_subtree;
44
45/// \{
46/// Check if \c T is a \c std::expected and, if it is, get the type it holds.
47///
48/// Extraction functions are allowed to return either a bare \c T or a \c std::expected<T, ast_node_type>, so the
49/// machinery which deduces what an adapter extracts has to see through the latter.
50template <typename T>
51struct is_expected :
52 std::false_type
53{ };
54
55template <typename T, typename E>
56struct is_expected<std::expected<T, E>> :
57 std::true_type
58{ };
59
60template <typename T>
61inline constexpr bool is_expected_v = is_expected<T>::value;
62
63template <typename T>
64struct expected_value_or_self
65{
66 using type = T;
67};
68
69template <typename T, typename E>
70struct expected_value_or_self<std::expected<T, E>>
71{
72 using type = T;
73};
74
75template <typename T>
76using expected_value_or_self_t = typename expected_value_or_self<T>::type;
77/// \}
78
79}
80
81/// \addtogroup Serialization
82/// \{
83
84/// Exception thrown if there is any problem running \c extract.
86 public std::runtime_error
87{
88public:
89 /// Description of a single problem with extraction.
90 class problem
91 {
92 public:
93 /// \{
94 /// Create a problem for the given \a path, \a message, and optional \a cause.
95 explicit problem(jsonv::path path, std::string message, std::exception_ptr cause) noexcept;
96 explicit problem(jsonv::path path, std::string message) noexcept;
97 /// \}
98
99 /// Create a problem with a \c message extracted from \a cause.
100 ///
101 /// \param cause The underlying cause of this problem to extract the message from. If the exception backing
102 /// \a cause is not derived from \c std::exception, a message about unknown exception will be used
103 /// instead.
104 explicit problem(jsonv::path path, std::exception_ptr cause) noexcept;
105
106 /// The path this problem was encountered at.
109 {
110 return _path;
111 }
112
113 /// Human-readable details about the encountered problem.
115 const std::string& message() const noexcept
116 {
117 return _message;
118 }
119
120 /// If there was an exception that caused this problem, extra details can be found in the nested exception. This
121 /// can be \c nullptr if there was no underlying cause.
123 const std::exception_ptr& nested_ptr() const noexcept
124 {
125 return _cause;
126 }
127
128 private:
129 jsonv::path _path;
130 std::string _message;
131 std::exception_ptr _cause;
132 };
133
134 using problem_list = std::vector<problem>;
135
136public:
137 /// Create an \c extraction_error from the given list of \a problems.
138 ///
139 /// \param problems The list of problems which caused this error. It is expected that \c problems.size() is greater
140 /// than \c 0. If it is not, a single \c problem will be created with a note about an unspecified
141 /// error.
142 explicit extraction_error(problem_list problems) noexcept;
143
144 /// \{
145 /// Create a new \c extraction_error with a single \c problem from the given \a path, \a message, and optional
146 /// underlying \a cause.
147 explicit extraction_error(jsonv::path path, std::string message, std::exception_ptr cause) noexcept;
148 explicit extraction_error(jsonv::path path, std::string message) noexcept;
149 explicit extraction_error(jsonv::path path, std::exception_ptr cause) noexcept;
150 /// \}
151
152 virtual ~extraction_error() noexcept;
153
154 /// Get the path the first extraction error came from.
157
158 /// Get the first \c problem::cause. This can be \c nullptr if the first \c problem does not have an underlying
159 /// cause.
162
163 /// Get the list of problems which caused this \c extraction_error. There will always be at least one \c problem in
164 /// this list.
166 const problem_list& problems() const noexcept { return _problems; }
167
168private:
169 template <typename... TArgs>
170 explicit extraction_error(std::in_place_t, TArgs&&... problem_args) noexcept;
171
172private:
173 problem_list _problems;
174};
175
176/// Configuration for various extraction options. This becomes part of the \c extraction_context.
178{
179public:
180 using size_type = extraction_error::problem_list::size_type;
181
182 /// When an error is encountered during extraction, what should happen?
183 enum class on_error
184 {
185 /// Report the first problem and stop, so the \c extraction_error thrown describes one thing that went wrong.
186 fail_immediately,
187 /// Keep extracting past a problem wherever something knows how to resume, so the \c extraction_error thrown
188 /// at the end describes as many of them as it can.
189 ///
190 /// Resuming is only possible where a composite knows where its next element begins -- the next element of an
191 /// array, the next key of an object -- which is why \c extraction_context::recover is asked rather than told.
192 /// A failure with no enclosing composite to resume into still ends extraction with a single problem.
193 ///
194 /// Collecting gathers diagnostics; it does not produce partially-extracted objects. An extraction which
195 /// recovered from anything still throws, so this changes how much the error explains and never whether one
196 /// happens.
197 ///
198 /// \see extract_options::max_failures
199 collect_all,
200 };
201
202 /// When an object key has the same value as a previously-seen key, what should happen?
204 {
205 /// Replace the previous value with the new one. The final value of the key in the object will be the
206 /// last-encountered one.
207 ///
208 /// For example: `{ "a": 1, "a": 2, "a": 3 }` will end with `{ "a": 3 }`.
209 replace,
210 /// Ignore the new values. The final value of the key in the object will be the first-encountered one.
211 ///
212 /// For example: `{ "a": 1, "a": 2, "a": 3 }` will end with `{ "a": 1 }`.
213 ignore,
214 /// Repeated keys should raise an \c extraction_error.
215 exception,
216 };
217
218public:
219 /// Create an instance with the default options.
221
223
224 /// Create a default set of options.
226 static extract_options create_default();
227
228 /// \{
229 /// See \c on_error. The default failure mode is \c fail_immediately.
231 on_error failure_mode() const noexcept { return _failure_mode; };
232 extract_options& failure_mode(on_error mode);
233 /// \}
234
235 /// \{
236 /// The number of problems to collect before giving up. This is only applicable if the \c failure_mode is
237 /// \c on_error::collect_all. By default, this value is 10.
238 ///
239 /// This is a threshold extraction stops at rather than a cap on the list it reports. A single failure which
240 /// reports several problems at once -- an adapter recording a batch of them before returning, or throwing an
241 /// \c extraction_error carrying several -- is taken whole rather than torn in half, so the final list can exceed
242 /// the limit by that batch. Truncating would drop diagnostics to enforce a bound whose purpose is to stop the
243 /// walk, not to edit the report.
244 ///
245 /// A limit of \c 0 or \c 1 makes the first problem the last, which is \c on_error::fail_immediately in all but
246 /// name.
247 ///
248 /// You should probably not set this value to an unreasonably high number, as each error encountered must be stored
249 /// in memory for some period of time.
251 size_type max_failures() const { return _max_failures; }
252 extract_options& max_failures(size_type limit);
253 /// \}
254
255 /// \{
256 /// See \c duplicate_key_action. The default action is \c replace.
258 duplicate_key_action on_duplicate_key() const { return _on_duplicate_key; }
259 extract_options& on_duplicate_key(duplicate_key_action action);
260 /// \}
261
262private:
263 // For the purposes of ABI compliance, most modifications to the variables in this class should bump the minor
264 // version number.
265 on_error _failure_mode = on_error::fail_immediately;
266 size_type _max_failures = 10U;
267 duplicate_key_action _on_duplicate_key = duplicate_key_action::replace;
268};
269
270/// An \c extractor holds the method for converting JSON source into an arbitrary C++ type.
272{
273public:
274 virtual ~extractor() noexcept;
275
276 /// Get the run-time type this \c extractor knows how to extract. Once this \c extractor is registered with a
277 /// \c formats, it is not allowed to change.
279 virtual const std::type_info& get_type() const noexcept = 0;
280
281 /// Extract the type \a from a \c reader \a into a region of memory.
282 ///
283 /// \param context Extra information to help you decode sub-objects, such as looking up other \c extractor
284 /// implementations via \c formats. It is also where a \ref extraction_context::problem is recorded
285 /// and where the \c path a problem is reported at comes from.
286 /// \param from The JSON \c reader to extract something from. On entry, \c reader::current is the first node of the
287 /// value to extract; on a successful return it should be one position past that value, as
288 /// \c reader::next_value would leave it.
289 /// \param into The region of memory to create the extracted object in. There will always be enough room to create
290 /// your object and the alignment of the pointer should be correct (assuming a working \c alignof
291 /// implementation).
292 ///
293 /// \returns A success result if the object was created in \a into; otherwise a \c std::unexpected carrying the
294 /// \c ast_node_type actually found when the failure was a type mismatch, or \c ast_node_type::error as a
295 /// sentinel for everything else. In the failure case nothing has been constructed in \a into and
296 /// \c extraction_context::problems describes what went wrong.
297 ///
298 /// \see extractor_for
299 /// \see adapter_for
300 /// \see value_adapter_for
304};
305
306/// Provides extra information to routines used for extraction, collects the problems they encounter, and tracks where
307/// in the document they are.
308///
309/// Unlike a \c serialization_context, this is mutable and single-use: recording a problem changes it. It is neither
310/// copyable nor movable, since a \ref path_scope holds a pointer to the instance it was pushed onto.
313{
314public:
315 using problem_list = extraction_error::problem_list;
316
317 class path_scope;
318
319public:
320 /// Create a new instance using the default \c formats (\c formats::global).
322
323 /// Create a new instance using the given \a fmt, \a ver, \a p, \a userdata and \a options.
324 ///
325 /// \param p A path all reported problems are relative to. This is almost always empty -- it exists for extraction
326 /// of a document which is itself a fragment of some larger one.
327 /// \param options What to do when something goes wrong. The default reports the first problem and stops; see
328 /// \c extract_options::on_error.
330 std::optional<jsonv::version> ver = std::nullopt,
332 const void* userdata = nullptr,
334 );
335
336 extraction_context(const extraction_context&) = delete;
337 extraction_context& operator=(const extraction_context&) = delete;
338
340
341 /// Is the \c value being extracted storage the pipeline materialised for the occasion, rather than storage the
342 /// caller handed in?
343 ///
344 /// An extractor which returns a view of what it was given must check this and refuse when it is \c true, because
345 /// the storage its view would name is destroyed as the bridge unwinds. \c std::string_view is the built-in one;
346 /// the situation arises whenever a \c value -based adapter runs against a reader over JSON text, since there is
347 /// no pre-existing tree for it to borrow and one has to be built.
348 ///
349 /// This is true for everything nested under such a materialisation, not only the value that caused it: a
350 /// \c std::vector<std::string_view> extracted from text is materialised once at the container and each element
351 /// then borrows from that temporary.
353 bool source_is_temporary() const noexcept { return _materialised_depth != 0U; }
354
355 /// Get the options this context is extracting under.
357 const extract_options& options() const noexcept { return _options; }
358
359 /// Get the path currently being extracted, as named by the live \ref path_scope guards.
360 ///
361 /// This is built on demand by walking the scope chain, so it is not free -- but nothing on a successful extraction
362 /// calls it. If no scope is live the result is the base path this context was created with, which is usually empty;
363 /// see \ref path_scope for why that is not the same as "the root of the document".
366
367 /// Note that a problem has been encountered, forwarding \a args to an \c extraction_error::problem.
368 ///
369 /// \returns \c std::unexpected of \c ast_node_type::error in all cases, which converts implicitly into any
370 /// \c std::expected<T, ast_node_type>, so an implementation can simply return it:
371 ///
372 /// \code
373 /// if (*result < 500 || *result > 2500)
374 /// return context.problem(context.path(), "Expected a value between 500 and 2500");
375 /// \endcode
376 ///
377 /// Recording a problem does not throw. The entry point which started extraction throws a single
378 /// \c extraction_error carrying everything collected, once the pipeline has unwound.
379 template <typename... TArgs>
381 std::unexpected<ast_node_type> problem(TArgs&&... args)
382 {
383 _problems.emplace_back(std::forward<TArgs>(args)...);
384 return std::unexpected(ast_node_type::error);
385 }
386
387 /// \{
388 /// Get the problems encountered so far. If this list is empty, no problems have occurred.
390 const problem_list& problems() const& { return _problems; }
392 problem_list&& problems() && { return std::move(_problems); }
393 /// \}
394
395 /// \{
396 /// May extraction recover from a failure and keep going?
397 ///
398 /// A composite which knows where its next element begins -- the next element of an array, the next key of an
399 /// object -- asks this when one of them fails. A \c true answer means skip what failed and keep walking, so one
400 /// bad element does not hide every problem after it; \c false means report the failure and let the pipeline
401 /// unwind. Only the loop knows where it would resume, which is why collecting is something a composite opts into
402 /// rather than something this context can deliver on its own -- and why a failure with no enclosing composite
403 /// ends extraction however \c extract_options::failure_mode is set.
404 ///
405 /// The answer is \c false under \c extract_options::on_error::fail_immediately, and becomes \c false in
406 /// \c collect_all once \c extract_options::max_failures problems have been recorded. It is never a promise that
407 /// extraction will succeed: recovering collects diagnostics, it does not produce partial objects, so a composite
408 /// which recovered from anything **must still report failure** once its loop is done.
409 ///
410 /// \code
411 /// auto element = context.extract<T>(from);
412 /// if (!element)
413 /// {
414 /// if (!context.recover())
415 /// return std::unexpected(element.error());
416 ///
417 /// recovered = true;
418 /// (void) from.next_value();
419 /// continue;
420 /// }
421 /// \endcode
422 ///
423 /// The overload taking an \c extraction_error is for an adapter on the \c value bridge, which reports failure by
424 /// throwing. On \c true the problems \a ex carries have been folded onto this context and the caller may
425 /// continue; on \c false nothing was folded and the caller should rethrow \a ex, which the catch in
426 /// \c extract(const std::type_info&, reader&, void*) folds instead. Either way every problem is recorded exactly
427 /// once, which is the thing to preserve: the \c value -based overloads hand their problems to the exception
428 /// rather than leaving them behind, so a fold in both places would report each failure twice.
432 bool recover(const extraction_error& ex);
433 /// \}
434
435 /// Remove and return the problems recorded since \a mark, a value \c problems() previously reported the size of.
436 ///
437 /// A composite which recovered still has to report failure, and on the \c value -based interface that means
438 /// throwing an \c extraction_error. This is how it hands over what it collected without leaving a copy behind for
439 /// the catch which folds that error back onto a context to record a second time.
441 problem_list take_problems_since(problem_list::size_type mark);
442
443 /// \{
444 /// Check that the \c reader::current AST node of \a from has the given \a type or is one of the given \a types. If
445 /// it is not, a \ref problem describing the mismatch is recorded and the type actually found is returned.
446 ///
447 /// This is \c reader::expect plus the human-readable message, which lives here because this is the layer that has
448 /// the path and the problem list to attach it to.
449 ///
450 /// \see current_as
451 /// \see reader::expect
456 /// \}
457
458 /// Get the \c reader::current AST node of \a from as a \c TAstNode, recording a \ref problem if it is some other
459 /// type.
460 ///
461 /// \see expect
462 /// \see reader::current_as
466 {
467 // Written as an explicit branch rather than `expect(...).transform(...)` for the same reason
468 // `reader::current_as` is: the monadic operations on `std::expected` postdate the type, so using one here
469 // would quietly raise the minimum toolchain by a release.
470 if (auto matched = expect(from, TAstNode::type()); !matched)
471 return std::unexpected(matched.error());
472 else
473 return from.current().as<TAstNode>();
474 }
475
476 /// Where to report a problem noticed while \a from is sitting on the thing that is wrong.
477 ///
478 /// This is \ref path when any \ref path_scope has named a position and the reader's own \c reader::current_path
479 /// when none has -- never both, since an adapter walking a single reader would otherwise have its position
480 /// counted twice. \ref expect and \ref current_as report through this; an extractor which rejects a value for a
481 /// reason other than its node type -- a number outside the range of what it builds, say -- wants the same answer
482 /// for the same reason.
483 ///
484 /// It is not free: on a text-backed reader with no scope live, \c reader::current_path rescans from the start of
485 /// the document. Ask for it when recording a problem, not before one happens.
488
489 /// \{
490 /// Attempt to extract a \c T from \a from using the \c formats associated with this context.
491 ///
492 /// \tparam T is the type to extract. It must be movable.
493 template <typename T>
495 std::expected<T, ast_node_type> extract(reader& from)
496 {
497 alignas(T) std::byte place[sizeof(T)];
498 if (auto result = extract(typeid(T), from, static_cast<void*>(place)); !result)
499 return std::unexpected(result.error());
500
501 T* ptr = std::launder(reinterpret_cast<T*>(place));
502 auto destroy = detail::on_scope_exit([ptr] { std::destroy_at(ptr); });
503 return std::move(*ptr);
504 }
505
507 std::expected<void, ast_node_type> extract(const std::type_info& type, reader& from, void* into);
508 /// \}
509
510 /// \{
511 /// Attempt to extract a \c T from the in-memory \a from using the \c formats associated with this context.
512 ///
513 /// These run the same pipeline as the \c reader overloads by walking \a from through a \c reader::from_value, and
514 /// report failure by throwing rather than by returning. They are how an adapter written against the older
515 /// \c value-based interface reaches the rest of the pipeline.
516 ///
517 /// \throws extraction_error if anything goes wrong when attempting to extract a value.
518 ///
519 /// \see value_adapter_for
520 template <typename T>
523 {
524 alignas(T) std::byte place[sizeof(T)];
525 extract(typeid(T), from, static_cast<void*>(place));
526 T* ptr = std::launder(reinterpret_cast<T*>(place));
527 auto destroy = detail::on_scope_exit([ptr] { std::destroy_at(ptr); });
528 return std::move(*ptr);
529 }
530
531 void extract(const std::type_info& type, const value& from, void* into);
532 /// \}
533
534 /// \{
535 /// Attempt to extract a \c T from <tt>from.at_path(subpath)</tt> using the \c formats associated with this context,
536 /// reporting any problem under \a subpath.
537 ///
538 /// \throws extraction_error if anything goes wrong when attempting to extract a value.
539 template <typename T>
542 {
543 alignas(T) std::byte place[sizeof(T)];
544 extract_sub(typeid(T), from, std::move(subpath), static_cast<void*>(place));
545 T* ptr = std::launder(reinterpret_cast<T*>(place));
546 auto destroy = detail::on_scope_exit([ptr] { std::destroy_at(ptr); });
547 return std::move(*ptr);
548 }
549
550 void extract_sub(const std::type_info& type, const value& from, jsonv::path subpath, void* into);
551
552 template <typename T>
554 T extract_sub(const value& from, path_element elem)
555 {
556 return extract_sub<T>(from, jsonv::path({ std::move(elem) }));
557 }
558 /// \}
559
560 /// An RAII guard naming one step of the extraction path while it is alive.
561 ///
562 /// A \c reader knows where the *reader* is, which is not always where the *extractor* is: an adapter which
563 /// re-roots onto a subtree gets a reader whose \c reader::current_path is relative to that subtree, and an adapter
564 /// which renames a member wants the name the caller declared rather than the one the document used. Pushing a
565 /// scope says where the extractor is, and takes precedence over the reader's own answer.
566 ///
567 /// Scopes are kept on the C++ stack and linked into a chain, so a push is two stores and a pop is one. No
568 /// \c jsonv::path is built until something calls \c extraction_context::path, which happens only when a problem is
569 /// recorded.
570 ///
571 /// The \c std::size_t and \c std::string_view overloads allocate nothing; the \a key of the latter must outlive
572 /// the scope, which is why an owning \c path_element overload exists for the callers that cannot promise it (a key
573 /// decoded from an \c ast_node_type::key_escaped node, for instance).
575 {
576 public:
577 path_scope(extraction_context& context, std::size_t index) noexcept;
578 path_scope(extraction_context& context, std::string_view key) noexcept;
580
581 /// Push every element of \a subpath at once. \a subpath must outlive the scope.
583
584 path_scope(const path_scope&) = delete;
585 path_scope& operator=(const path_scope&) = delete;
586
588
589 private:
591
592 /// Append this scope's ancestors and then itself to \a out, so the result reads outermost-first.
593 void append_to(jsonv::path& out) const;
594
595 private:
596 extraction_context* _context;
597 const path_scope* _parent;
598 std::variant<std::size_t, std::string_view, path_element, const jsonv::path*> _element;
599 };
600
601private:
603 friend class detail::borrowed_subtree;
604
605 /// Where to report a failure which is being translated out of an exception. Unlike \ref problem_path this takes
606 /// the location a bridge left behind on its way out, because by now the cursor has moved on from the value which
607 /// failed. Taking it is the point: it belongs to the failure being translated and to nothing after it.
609 jsonv::path take_failure_path(const reader& from);
610
611private:
612 extract_options _options;
613 jsonv::path _base_path;
614 const path_scope* _innermost = nullptr;
615 std::size_t _materialised_depth = 0U;
616
617 /// Where to report the failure currently unwinding, left by a bridge which walked the cursor past the value which
618 /// failed. A destructor runs before the handler which records the problem, so the location has to be worked out
619 /// in the destructor and picked up by \ref take_failure_path. It lives and dies with one call to \c extract.
620 std::optional<jsonv::path> _failure_path;
621
622 problem_list _problems;
623};
624
625/// Consume the JSON subtree under \a from starting at \c reader::current and return it as a fully materialised \c value
626/// tree.
627///
628/// On return \a from has advanced one position past the consumed subtree, exactly as \c reader::next_value would have
629/// left it for the same input. Every adapter reading a subtree has to agree on this, or subtrees get consumed twice or
630/// not at all. A leading \c ast_node_type::document_start is stepped over first, so this works on a freshly-created
631/// reader as well as on one positioned mid-document.
632///
633/// This is the bridge which lets adapters written against the older \c value -based interface keep working while the
634/// surrounding pipeline runs against a streaming \c reader.
635///
636/// \throws extraction_error if \a from is not positioned on a value or the document ends part-way through one.
637///
638/// \see value_adapter_for
640
641namespace detail
642{
643
644/// The subtree under a reader's cursor as a \c value, borrowed rather than copied when the reader can lend it.
645///
646/// A reader created by \c reader::from_value is already holding the tree the older \c value -based interface wants.
647/// Materialising a copy for it would pay for a deep copy and, worse, hand the adapter storage which dies with this
648/// object -- silently breaking every extractor which returns a view of what it was given. Borrowing is what keeps a
649/// \c std::string_view pointing into the caller's \c value, which is where it pointed before extraction ran through
650/// a \c reader.
651///
652/// A reader over JSON text has no such tree, so the subtree is materialised. Anything borrowed from it is valid only
653/// until this object goes away, which is why the context is told: see \c extraction_context::source_is_temporary.
654///
655/// **The reader is not advanced until \c commit.** An adapter on the bridge consumes its whole subtree before the
656/// older body runs, so a failure in that body would otherwise be reported against the next sibling. Leaving the cursor
657/// where the extraction started means the position is simply still correct, which is cheaper and more accurate than
658/// noting it beforehand -- \c reader::current_path rebuilds by scanning from the start of the document on a
659/// text-backed source, so asking for it on every successful extraction is quadratic. A structure read from text is the
660/// one case which cannot wait, since materialising it is what walks the cursor over it.
661class JSONV_PUBLIC borrowed_subtree
662{
663public:
664 borrowed_subtree(extraction_context& context, reader& from);
665
666 borrowed_subtree(const borrowed_subtree&) = delete;
667 borrowed_subtree& operator=(const borrowed_subtree&) = delete;
668
669 ~borrowed_subtree() noexcept;
670
672 const value& get() const noexcept { return _borrowed ? *_borrowed : _owned; }
673
674 /// Step the reader past the subtree, if it is not already past it. Call this once the older body has succeeded;
675 /// skipping it on failure is what leaves the cursor naming the value which failed.
676 void commit();
677
678private:
679 extraction_context* _context;
680 reader* _from;
681 const value* _borrowed;
682 bool _materialised;
683 bool _advanced;
684 int _uncaught_on_entry;
685 value _owned;
686};
687
688/// Call \a func as an extraction function and normalise whatever it gives back into a \c std::expected.
689///
690/// Four call shapes are accepted, tried in this order: <tt>(context, reader)</tt>, <tt>(reader)</tt>,
691/// <tt>(context, value)</tt>, <tt>(value)</tt>. The last two are the interface functions were written against before
692/// extraction ran off a \c reader; they get a subtree materialised by \c read_value. Either a bare \c T or a
693/// \c std::expected<T, ast_node_type> is an acceptable return.
694template <typename T, typename FExtract>
696std::expected<T, ast_node_type> invoke_extract(const FExtract& func, extraction_context& context, reader& from)
697{
698 auto normalise = [](auto&& result) -> std::expected<T, ast_node_type>
699 {
700 if constexpr (is_expected_v<std::remove_cvref_t<decltype(result)>>)
701 {
702 if (result)
703 return std::move(result).value();
704 else
705 return std::unexpected(result.error());
706 }
707 else
708 {
709 return std::forward<decltype(result)>(result);
710 }
711 };
712
713 if constexpr (std::invocable<const FExtract&, extraction_context&, reader&>)
714 {
715 return normalise(func(context, from));
716 }
717 else if constexpr (std::invocable<const FExtract&, reader&>)
718 {
719 return normalise(func(from));
720 }
721 else if constexpr (std::invocable<const FExtract&, extraction_context&, const value&>)
722 {
723 // `get()` is a `const value&`, which is the signature the `invocable` check above tested. Handing over a
724 // mutable one would let a callable overloaded on both pick the other overload.
725 borrowed_subtree subtree(context, from);
726 auto result = normalise(func(context, subtree.get()));
727 if (result)
728 subtree.commit();
729 return result;
730 }
731 else
732 {
733 static_assert(std::invocable<const FExtract&, const value&>,
734 "An extraction function must be callable as (extraction_context&, reader&), (reader&), "
735 "(extraction_context&, const value&) or (const value&)"
736 );
737
738 borrowed_subtree subtree(context, from);
739 auto result = normalise(func(subtree.get()));
740 if (result)
741 subtree.commit();
742 return result;
743 }
744}
745
746/// The type an extraction function extracts: its return type, with a \c std::expected unwrapped, deduced from the
747/// same four call shapes \c invoke_extract accepts and in the same order.
748template <typename FExtract>
749struct extract_function_result
750{
751 static auto deduce()
752 {
753 if constexpr (std::invocable<const FExtract&, extraction_context&, reader&>)
754 return std::type_identity<std::invoke_result_t<const FExtract&, extraction_context&, reader&>>();
755 else if constexpr (std::invocable<const FExtract&, reader&>)
756 return std::type_identity<std::invoke_result_t<const FExtract&, reader&>>();
757 else if constexpr (std::invocable<const FExtract&, extraction_context&, const value&>)
758 return std::type_identity<std::invoke_result_t<const FExtract&, extraction_context&, const value&>>();
759 else
760 return std::type_identity<std::invoke_result_t<const FExtract&, const value&>>();
761 }
762
763 using type = expected_value_or_self_t<std::remove_cvref_t<typename decltype(deduce())::type>>;
764};
765
766template <typename FExtract>
767using extract_function_result_t = typename extract_function_result<FExtract>::type;
768
769}
770
771/// Extract a C++ value from \a from using the provided \a fmts.
772template <typename T>
774T extract(const value& from, const formats& fmts)
775{
777 return context.extract<T>(from);
778}
779
780/// Extract a C++ value from \a from using the provided \a fmts and \a options.
781template <typename T>
783T extract(const value& from, const formats& fmts, const extract_options& options)
784{
785 extraction_context context(fmts, std::nullopt, jsonv::path(), nullptr, options);
786 return context.extract<T>(from);
787}
788
789/// Extract a C++ value from \a from using \c jsonv::formats::global().
790template <typename T>
793{
795 return context.extract<T>(from);
796}
797
798/// Extract a C++ value from \a from using \c jsonv::formats::global() and the provided \a options.
799template <typename T>
801T extract(const value& from, const extract_options& options)
802{
803 extraction_context context(formats::global(), std::nullopt, jsonv::path(), nullptr, options);
804 return context.extract<T>(from);
805}
806
807/// \}
808
809}
Utilities for directly dealing with a JSON AST.
Provides extra information to routines used for extraction and serialization.
Definition context.hpp:26
An adapter for enumeration types.
Configuration for various extraction options. This becomes part of the extraction_context.
Definition extract.hpp:178
extract_options() noexcept
Create an instance with the default options.
duplicate_key_action
When an object key has the same value as a previously-seen key, what should happen?
Definition extract.hpp:204
size_type max_failures() const
Definition extract.hpp:251
on_error
When an error is encountered during extraction, what should happen?
Definition extract.hpp:184
duplicate_key_action on_duplicate_key() const
Definition extract.hpp:258
An RAII guard naming one step of the extraction path while it is alive.
Definition extract.hpp:575
path_scope(extraction_context &context, const jsonv::path &subpath) noexcept
Push every element of subpath at once. subpath must outlive the scope.
Provides extra information to routines used for extraction, collects the problems they encounter,...
Definition extract.hpp:313
jsonv::path path() const
Get the path currently being extracted, as named by the live path_scope guards.
T extract(const value &from)
Definition extract.hpp:522
const extract_options & options() const noexcept
Get the options this context is extracting under.
Definition extract.hpp:357
T extract_sub(const value &from, jsonv::path subpath)
Definition extract.hpp:541
const problem_list & problems() const &
Definition extract.hpp:390
std::unexpected< ast_node_type > problem(TArgs &&... args)
Note that a problem has been encountered, forwarding args to an extraction_error::problem.
Definition extract.hpp:381
bool recover() const noexcept
extraction_context()
Create a new instance using the default formats (formats::global).
std::expected< T, ast_node_type > extract(reader &from)
Definition extract.hpp:495
jsonv::path problem_path(const reader &from) const
Where to report a problem noticed while from is sitting on the thing that is wrong.
extraction_context(jsonv::formats fmt, std::optional< jsonv::version > ver=std::nullopt, jsonv::path p=jsonv::path(), const void *userdata=nullptr, extract_options options=extract_options())
Create a new instance using the given fmt, ver, p, userdata and options.
Description of a single problem with extraction.
Definition extract.hpp:91
const std::string & message() const noexcept
Human-readable details about the encountered problem.
Definition extract.hpp:115
const std::exception_ptr & nested_ptr() const noexcept
If there was an exception that caused this problem, extra details can be found in the nested exceptio...
Definition extract.hpp:123
problem(jsonv::path path, std::exception_ptr cause) noexcept
Create a problem with a message extracted from cause.
problem(jsonv::path path, std::string message, std::exception_ptr cause) noexcept
const jsonv::path & path() const noexcept
The path this problem was encountered at.
Definition extract.hpp:108
Exception thrown if there is any problem running extract.
Definition extract.hpp:87
extraction_error(jsonv::path path, std::string message, std::exception_ptr cause) noexcept
extraction_error(problem_list problems) noexcept
Create an extraction_error from the given list of problems.
An extractor holds the method for converting JSON source into an arbitrary C++ type.
Definition extract.hpp:272
virtual const std::type_info & get_type() const noexcept=0
Get the run-time type this extractor knows how to extract.
Simply put, this class is a collection of extractor and serializer instances.
Definition formats.hpp:160
Represents an exact path in some JSON structure.
Definition path.hpp:88
A reader instance reads from some form of JSON source (probably a string) and converts it into a JSON...
Definition reader.hpp:96
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) 2015-2020 by Travis Gockel.
T extract(const value &from, const formats &fmts)
Extract a C++ value from from using the provided fmts.
Definition extract.hpp:774
@ 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.
Support for JSONPath.
Read a JSON AST.
Definition of the on_scope_exit utility.
Copyright (c) 2012-2020 by Travis Gockel.