JSON Voorhees
Killer JSON for C++
Loading...
Searching...
No Matches
jsonv::deserialization_context Class Reference

Provides extra information to routines used for deserialization, collects the problems they encounter, and tracks where in the document they are. More...

#include <jsonv/serialization/deserialize.hpp>

+ Inheritance diagram for jsonv::deserialization_context:
+ Collaboration diagram for jsonv::deserialization_context:

Classes

class  path_scope
 An RAII guard naming one step of the deserialization path while it is alive. More...
 

Public Types

using problem_list = deserialization_error::problem_list
 The problems recorded on a context, in the form a deserialization_error carries them.
 

Public Member Functions

 deserialization_context ()
 Create a new instance using the default formats (formats::global).
 
 deserialization_context (jsonv::formats fmt, std::optional< jsonv::version > ver=std::nullopt, jsonv::path p=jsonv::path(), const void *userdata=nullptr, deserialize_options options=deserialize_options(), std::string source_name=std::string())
 Create a new instance using the given fmt, ver, p, userdata, options and source_name.
 
 deserialization_context (const deserialization_context &)=delete
 
deserialization_context & operator= (const deserialization_context &)=delete
 
bool source_is_temporary () const noexcept
 Is the source being deserialized storage which is freed when deserialization finishes, rather than storage the caller keeps?
 
const deserialize_options & options () const noexcept
 Get the options this context is deserializing under.
 
const std::string & source_name () const noexcept
 Get the name of the document being deserialized, which every problem recorded here is reported in.
 
std::string_view encoded_source () const
 Get the JSON of the object a type described with the serialization builder DSL is being deserialized from, from its { to its matching }.
 
optional< const value & > source_value () const
 Get the value a type described with the serialization builder DSL is being deserialized from, for its hooks to read members out of: a version for pre_deserialize to refuse a document by, a sibling for a default_value to compute from, the values of the keys an on_unknown_members handler is told no member claimed.
 
jsonv::path path () const
 Get the path currently being deserialized, as named by the live path_scope guards.
 
template<typename... TArgs>
std::unexpected< ast_node_type > problem (TArgs &&... args)
 Note that a problem has been encountered, forwarding args to a deserialization_error::problem.
 
problem_list take_problems_since (problem_list::size_type mark)
 Remove and return the problems recorded since mark, a value problems() previously reported the size of.
 
void skip_failed_value (reader &from) noexcept
 Step from past the value whose failure is being recovered from.
 
void note_value_consumed (const reader &from) noexcept
 Note that the failure about to be reported has already consumed, from from, the value it failed on, so whatever recovers from it must not step over that value a second time.
 
template<typename TAstNode >
std::expected< TAstNode, ast_node_type > current_as (reader &from)
 Get the reader::current AST node of from as a TAstNode, recording a problem if it is some other type.
 
jsonv::path problem_path (const reader &from) const
 Where to report a problem noticed while from is sitting on the thing that is wrong.
 
template<typename T >
std::expected< T, ast_node_type > deserialize (reader &from)
 Attempt to deserialize a T from from using the formats associated with this context.
 
std::expected< void, ast_node_type > deserialize (const std::type_info &type, reader &from, void *into)
 Attempt to deserialize an object of the given type from from into the memory at into, using the formats associated with this context.
 
template<typename T >
T deserialize (const value &from)
 Attempt to deserialize a T from the in-memory from using the formats associated with this context.
 
const problem_list & problems () const &
 Get the problems encountered so far. If this list is empty, no problems have occurred.
 
problem_list && problems () &&
 Get the problems encountered so far. If this list is empty, no problems have occurred.
 
bool recover () const noexcept
 May deserialization recover from a failure and keep going?
 
bool recover (const deserialization_error &ex)
 May deserialization recover from a failure and keep going?
 
std::expected< void, ast_node_type > expect (reader &from, ast_node_type type)
 Check that the reader::current AST node of from has the given type or is one of the given types.
 
std::expected< void, ast_node_type > expect (reader &from, std::initializer_list< ast_node_type > types)
 Check that the reader::current AST node of from has the given type or is one of the given types.
 
- Public Member Functions inherited from jsonv::context
 context ()
 Create a new instance using the default formats (formats::global).
 
 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.
 
const jsonv::formats & formats () const
 Get the formats object backing deserialization and encoding.
 
const std::optional< jsonv::version > & version () const
 Get the version this context was created with.
 
const void * user_data () const
 Get a pointer to arbitrary user data.
 

Friends

class path_scope
 

Detailed Description

Provides extra information to routines used for deserialization, collects the problems they encounter, and tracks where in the document they are.

Unlike a serialization_context, this is mutable and single-use: recording a problem changes it. It is neither copyable nor movable, since a path_scope holds a pointer to the instance it was pushed onto.

Most deserialization never sees one, since jsonv::deserialize builds its own. Build one to deserialize a document under a version, with user data, at a base path, or under the name of the file it came from (see source_name), and hand it to the jsonv::deserialize overloads which take one – one context for each document.

Definition at line 392 of file deserialize.hpp.

Member Typedef Documentation

◆ problem_list

using jsonv::deserialization_context::problem_list = deserialization_error::problem_list

The problems recorded on a context, in the form a deserialization_error carries them.

Definition at line 397 of file deserialize.hpp.

Constructor & Destructor Documentation

◆ deserialization_context()

jsonv::deserialization_context::deserialization_context ( jsonv::formats  fmt,
std::optional< jsonv::version >  ver = std::nullopt,
jsonv::path  p = jsonv::path(),
const void *  userdata = nullptr,
deserialize_options  options = deserialize_options(),
std::string  source_name = std::string() 
)
explicit

Create a new instance using the given fmt, ver, p, userdata, options and source_name.

Parameters
fmtThe formats to find a deserializer for each type in.
verThe version of the document being deserialized, for deserializers to read back with context::version. std::nullopt means no version was specified, which is not the same thing as version 0.0.
pA path all reported problems are relative to. This is almost always empty – it exists for deserialization of a document which is itself a fragment of some larger one.
userdataArbitrary data for deserializers to read back with context::user_data. It is not owned, so it must outlive this instance.
optionsWhat to do when something goes wrong. The default reports the first problem and stops; see deserialize_options::on_error.
source_nameThe name of the document being deserialized, such as the file it was read from, for every problem recorded here to be reported in. Empty, the default, names nothing. Spell out every argument before it: a string literal in the place of userdata is a const void* as far as the compiler is concerned, and would quietly become the user data instead.

Member Function Documentation

◆ current_as()

template<typename TAstNode >
std::expected< TAstNode, ast_node_type > jsonv::deserialization_context::current_as ( reader &  from)
inline

Get the reader::current AST node of from as a TAstNode, recording a problem if it is some other type.

See also
expect
reader::current_as

Definition at line 664 of file deserialize.hpp.

◆ deserialize() [1/2]

std::expected< void, ast_node_type > jsonv::deserialization_context::deserialize ( const std::type_info &  type,
reader &  from,
void *  into 
)

Attempt to deserialize an object of the given type from from into the memory at into, using the formats associated with this context.

This is what the overload above calls with the T it was asked for.

into must have room for an object of type and be suitably aligned for it. On success an object has been created there, and destroying it is up to the caller. On failure nothing has been created, the problem is recorded on this context, and the ast_node_type returned is the one the deserializer reported (see deserializer::deserialize). An exception thrown while deserializing is not propagated: it is recorded as a problem and reported as ast_node_type::error, except that a deserialization_error from an adapter on the value bridge has its own problems folded onto this context instead.

Called from a hook which can see source_value or encoded_source, this hides both from everything the deserialization runs, since none of that is part of the object the hook is about.

+ Here is the call graph for this function:

◆ deserialize() [2/2]

template<typename T >
std::expected< T, ast_node_type > jsonv::deserialization_context::deserialize ( reader &  from)
inline

Attempt to deserialize a T from from using the formats associated with this context.

This is the positioned primitive a composite calls for each of its parts: it deserializes the value under the cursor and nothing else. In particular it does not step over ast_node_type::document_start, so it is not the way to start on a fresh reader – jsonv::deserialize is.

Template Parameters
Tis the type to deserialize. It must be movable.

Definition at line 697 of file deserialize.hpp.

+ Here is the call graph for this function:

◆ encoded_source()

std::string_view jsonv::deserialization_context::encoded_source ( ) const

Get the JSON of the object a type described with the serialization builder DSL is being deserialized from, from its { to its matching }.

Where source_name says which document a problem is in, this quotes the object it is about, for the hooks which validate one to put in their message.

It is only there once the walk has reached the object's }, which is to say for what runs after it: on_unknown_members, every member's default_value and the setter that default is handed to, and post_deserialize. What runs before or during the walk is shown nothing: pre_deserialize, which can have the object as a value from source_value but not its text, and a member's check and setter as its key is read. Neither is anything a hook goes on to deserialize through deserialize – or through jsonv::deserialize, which comes through it – whichever deserializer that reaches, nor anything outside DSL deserialization altogether. No object's source is empty – the least of them is {} – so an empty view always means there is nothing to quote.

A deserializer reached around deserialize, through formats::deserialize or by calling it directly, skips the hiding along with everything else deserialize does around the call, and is shown whatever is showing.

Read from JSON text, this is a view of exactly what was written, whitespace, comments and keys no member claimed included. Read from a value, there was never any text to view, so it is that value's compact encoding – faithful to the JSON, but not necessarily the bytes anybody typed. It is encoded on the first call and kept for the rest of that object's hooks, so a deserialization which never asks never pays for it.

Returns
A view which is valid until the hook which asked for it returns.

◆ expect() [1/2]

std::expected< void, ast_node_type > jsonv::deserialization_context::expect ( reader &  from,
ast_node_type  type 
)

Check that the reader::current AST node of from has the given type or is one of the given types.

If it is not, a problem describing the mismatch is recorded and the type actually found is returned.

This is reader::expect plus the human-readable message, which lives here because this is the layer that has the path and the problem list to attach it to.

See also
current_as
reader::expect

◆ expect() [2/2]

std::expected< void, ast_node_type > jsonv::deserialization_context::expect ( reader &  from,
std::initializer_list< ast_node_type >  types 
)

Check that the reader::current AST node of from has the given type or is one of the given types.

If it is not, a problem describing the mismatch is recorded and the type actually found is returned.

This is reader::expect plus the human-readable message, which lives here because this is the layer that has the path and the problem list to attach it to.

See also
current_as
reader::expect

◆ note_value_consumed()

void jsonv::deserialization_context::note_value_consumed ( const reader &  from)
noexcept

Note that the failure about to be reported has already consumed, from from, the value it failed on, so whatever recovers from it must not step over that value a second time.

The value bridge says this for itself. An adapter which walks the reader has to say it whenever it fails with the value behind it rather than in front of it: after a nested deserialization which succeeded, or once it has read its own closing token. A composite which fails part-way through a structure should finish walking that structure first – the position inside it means nothing to a caller – and then say so.

See also
skip_failed_value

◆ options()

const deserialize_options & jsonv::deserialization_context::options ( ) const
inlinenoexcept

Get the options this context is deserializing under.

Definition at line 454 of file deserialize.hpp.

◆ path()

jsonv::path jsonv::deserialization_context::path ( ) const

Get the path currently being deserialized, as named by the live path_scope guards.

This is built on demand by walking the scope chain, so it is not free – but nothing on a successful deserialization calls it. If no scope is live the result is the base path this context was created with, which is usually empty; see path_scope for why that is not the same as "the root of the document".

◆ problem()

template<typename... TArgs>
std::unexpected< ast_node_type > jsonv::deserialization_context::problem ( TArgs &&...  args)
inline

Note that a problem has been encountered, forwarding args to a deserialization_error::problem.

Returns
std::unexpected of ast_node_type::error in all cases, which converts implicitly into any std::expected<T, ast_node_type>, so an implementation can simply return it:
if (*result < 500 || *result > 2500)
return context.problem(context.path(), "Expected a value between 500 and 2500");
Provides extra information to routines used for deserialization and serialization.
Definition context.hpp:26

Recording a problem does not throw. The entry point which started deserialization throws a single deserialization_error carrying everything collected, once the pipeline has unwound.

A problem which does not already name its source is recorded as being in this context's source_name. One folded in from the deserialization of some other document keeps the name it was given there.

Definition at line 542 of file deserialize.hpp.

◆ problem_path()

jsonv::path jsonv::deserialization_context::problem_path ( const reader &  from) const

Where to report a problem noticed while from is sitting on the thing that is wrong.

This is path when any path_scope has named a position and the reader's own reader::current_path when none has – never both, since an adapter walking a single reader would otherwise have its position counted twice. expect and current_as report through this; a deserializer which rejects a value for a reason other than its node type – a number outside the range of what it builds, say – wants the same answer for the same reason.

It is not free: on a text-backed reader with no scope live, reader::current_path rescans from the start of the document. Ask for it when recording a problem, not before one happens.

◆ problems() [1/2]

problem_list && jsonv::deserialization_context::problems ( ) &&
inline

Get the problems encountered so far. If this list is empty, no problems have occurred.

Definition at line 559 of file deserialize.hpp.

◆ problems() [2/2]

const problem_list & jsonv::deserialization_context::problems ( ) const &
inline

Get the problems encountered so far. If this list is empty, no problems have occurred.

Definition at line 557 of file deserialize.hpp.

◆ recover() [1/2]

bool jsonv::deserialization_context::recover ( ) const
noexcept

May deserialization recover from a failure and keep going?

A composite which knows where its next element begins – the next element of an array, the next key of an object – asks this when one of them fails. A true answer means skip what failed and keep walking, so one bad element does not hide every problem after it; false means report the failure and let the pipeline unwind. Only the loop knows where it would resume, which is why collecting is something a composite opts into rather than something this context can deliver on its own – and why a failure with no enclosing composite ends deserialization however deserialize_options::failure_mode is set.

The answer is false under deserialize_options::on_error::fail_immediately, and becomes false in collect_all once deserialize_options::max_failures problems have been recorded. It is never a promise that deserialization will succeed: recovering collects diagnostics, it does not produce partial objects, so a composite which recovered from anything must still report failure once its loop is done.

auto element = context.deserialize<T>(from);
if (!element)
{
if (!context.recover())
return std::unexpected(element.error());
recovered = true;
context.skip_failed_value(from);
continue;
}

Note skip_failed_value rather than reader::next_value: where the value which failed was read through the value bridge, the cursor is already past it and stepping again would skip the next one.

The overload taking a deserialization_error is for an adapter on the value bridge, which reports failure by throwing. On true the problems ex carries have been folded onto this context and the caller may continue; on false nothing was folded and the caller should rethrow ex, which the catch in deserialize(const std::type_info&, reader&, void*) folds instead. Either way every problem is recorded exactly once, which is the thing to preserve: the value -based overloads hand their problems to the exception rather than leaving them behind, so a fold in both places would report each failure twice.

◆ recover() [2/2]

bool jsonv::deserialization_context::recover ( const deserialization_error &  ex)

May deserialization recover from a failure and keep going?

A composite which knows where its next element begins – the next element of an array, the next key of an object – asks this when one of them fails. A true answer means skip what failed and keep walking, so one bad element does not hide every problem after it; false means report the failure and let the pipeline unwind. Only the loop knows where it would resume, which is why collecting is something a composite opts into rather than something this context can deliver on its own – and why a failure with no enclosing composite ends deserialization however deserialize_options::failure_mode is set.

The answer is false under deserialize_options::on_error::fail_immediately, and becomes false in collect_all once deserialize_options::max_failures problems have been recorded. It is never a promise that deserialization will succeed: recovering collects diagnostics, it does not produce partial objects, so a composite which recovered from anything must still report failure once its loop is done.

auto element = context.deserialize<T>(from);
if (!element)
{
if (!context.recover())
return std::unexpected(element.error());
recovered = true;
context.skip_failed_value(from);
continue;
}

Note skip_failed_value rather than reader::next_value: where the value which failed was read through the value bridge, the cursor is already past it and stepping again would skip the next one.

The overload taking a deserialization_error is for an adapter on the value bridge, which reports failure by throwing. On true the problems ex carries have been folded onto this context and the caller may continue; on false nothing was folded and the caller should rethrow ex, which the catch in deserialize(const std::type_info&, reader&, void*) folds instead. Either way every problem is recorded exactly once, which is the thing to preserve: the value -based overloads hand their problems to the exception rather than leaving them behind, so a fold in both places would report each failure twice.

◆ skip_failed_value()

void jsonv::deserialization_context::skip_failed_value ( reader &  from)
noexcept

Step from past the value whose failure is being recovered from.

This is reader::next_value, except where the step has already happened. Most deserializers leave the cursor naming the value they rejected, so stepping over it is exactly one reader::next_value. An adapter on the value bridge reading a structure out of JSON text is the exception: materialising that structure is what walks the cursor over it, so by the time the older body reports a failure the cursor names the next sibling. A loop recovering with a bare reader::next_value steps over that sibling as well, dropping it from the result and dropping every problem it had to report – and misnumbering everything after it.

The note this consults belongs to from and to the one failure being reported. It is cleared when the next deserialization starts, so a note nobody collects expires rather than answering for an unrelated position.

See also
recover
note_value_consumed

◆ source_is_temporary()

bool jsonv::deserialization_context::source_is_temporary ( ) const
inlinenoexcept

Is the source being deserialized storage which is freed when deserialization finishes, rather than storage the caller keeps?

A deserializer which returns a view of what it was given must check this and refuse when it is true, because the storage its view would name is gone by the time the caller has it. std::string_view is the built-in one. There are three ways to get here:

  • A value -based adapter runs against a reader over JSON text. There is no pre-existing tree for it to borrow, so one is materialised and destroyed as the bridge unwinds. This is true for everything nested under such a materialisation, not only the value that caused it. - The source was handed to deserialization to own: jsonv::deserialize given a std::string rvalue, or an rvalue reader which owns its source. That source dies with the call, so this is true for the whole deserialization. - A hook of a type described with the serialization builder DSL has been lent, by source_value, an object read out of JSON text for it. That object dies with the deserialization of the type, so this is true from then until the walk of the object starts or its deserialization finishes.

Definition at line 450 of file deserialize.hpp.

◆ source_name()

const std::string & jsonv::deserialization_context::source_name ( ) const
inlinenoexcept

Get the name of the document being deserialized, which every problem recorded here is reported in.

It is empty if this context was not given one.

See also
deserialization_error::problem::source_name

Definition at line 461 of file deserialize.hpp.

◆ source_value()

optional< const value & > jsonv::deserialization_context::source_value ( ) const

Get the value a type described with the serialization builder DSL is being deserialized from, for its hooks to read members out of: a version for pre_deserialize to refuse a document by, a sibling for a default_value to compute from, the values of the keys an on_unknown_members handler is told no member claimed.

It is there for the hooks which take a deserialization_context: pre_deserialize, before the walk, is shown the value the reader is on, which is not necessarily an object; and on_unknown_members, every member's default_value and post_deserialize, after it, are shown the object. A member's check and setter run during the walk and are shown nothing, as is a type_default_value standing in for a null. So, as for encoded_source, is anything a hook goes on to deserialize through deserialize, and anything outside DSL deserialization altogether.

Read from a value, this is that value: the caller's own tree, lent rather than copied. Read from JSON text there is no tree to lend, so the first hook to ask has the object read into one – from where the reader is, before the walk, or from the object's { after it, through a second cursor which leaves the first where it is. Every hook of that object after it is shown the same one. Nothing is read for a deserialization which never asks, and nothing allocated: what lets the hooks after the walk go back is a position on the parsed document's tape. A repeated key is settled by options as the walk settles it.

What is read from text belongs to the deserialization rather than to the caller, so while it is being lent source_is_temporary is true – and deserializing a std::string_view out of it is refused rather than left to dangle. That lasts until the walk starts or the object is finished.

Returns
The value, valid until the hook which asked for it returns; or nothing where no hook is being shown one.
Exceptions
Whateverread_value throws for the object, the first time it is read from text.

◆ take_problems_since()

problem_list jsonv::deserialization_context::take_problems_since ( problem_list::size_type  mark)

Remove and return the problems recorded since mark, a value problems() previously reported the size of.

A composite which recovered still has to report failure, and on the value -based interface that means throwing a deserialization_error. This is how it hands over what it collected without leaving a copy behind for the catch which folds that error back onto a context to record a second time.

Friends And Related Symbol Documentation

◆ path_scope

friend class path_scope
friend

Definition at line 824 of file deserialize.hpp.


The documentation for this class was generated from the following file: