JSON Voorhees
Killer JSON for C++
Loading...
Searching...
No Matches
jsonv::reader Class Referencefinal

A reader instance reads from some form of JSON source (probably a string) and converts it into a JSON ast_node sequence. More...

#include <jsonv/reader.hpp>

Public Member Functions

 reader (parse_index index)
 Create a reader which reads from the given index.
 
 reader (const reader &)=delete
 
reader & operator= (const reader &)=delete
 
bool good () const
 Check if this reader is still good to read from.
 
void validate () const
 Check that the source this reader was created from is valid JSON.
 
bool owns_source () const noexcept
 Does this reader own the storage it reads from?
 
const ast_node & current () const
 Get the current AST node this reader is pointing at.
 
ast_node_type current_type () const
 Get the type of the current AST node, which is always current().type().
 
template<typename TAstNode >
std::expected< TAstNode, ast_node_type > current_as () const
 Get the current AST node as a specific TAstNode subtype, calling expect beforehand.
 
optional< const value & > current_value () const noexcept
 Get the in-memory value this reader is positioned on, if it has one to lend.
 
const path & current_path () const
 Get the path to the current node this reader is pointing at.
 
bool next_token () noexcept
 Go to the next token.
 
bool next_structure () noexcept
 Go to one past the end of the current structure.
 
bool next_value () noexcept
 Go to one past the value this reader is on.
 
bool next_key ()
 Go to the next object key or end-of-object.
 
 reader (std::string_view source)
 Create a reader which reads from JSON source.
 
 reader (std::string_view source, const parse_options &parse_options)
 Create a reader which reads from JSON source.
 
 reader (const char *source)
 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 (std::string &&source)
 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 (reader &&) noexcept
 Moving a reader transfers its implementation, leaving the source moved-from: good is false and the accessors throw std::invalid_argument.
 
reader & operator= (reader &&) noexcept
 Moving a reader transfers its implementation, leaving the source moved-from: good is false and the accessors throw std::invalid_argument.
 
std::expected< void, ast_node_type > expect (ast_node_type type) const
 Check that the current AST node has the given type or is one of the expected types.
 
std::expected< void, ast_node_type > expect (std::initializer_list< ast_node_type > types) const
 Check that the current AST node has the given type or is one of the expected types.
 

Static Public Member Functions

static reader from_value (const value &value)
 Create a reader which reads from an in-memory value.
 
static reader from_value (value &&value)
 Create a reader which reads from an in-memory value.
 

Detailed Description

A reader instance reads from some form of JSON source (probably a string) and converts it into a JSON ast_node sequence.

Readers normalize access to JSON source for conversion to some other format. They can be provided with pre-parsed JSON through a parse_index or value. They can be provided with a std::string or std::string_view directly. This allows extractor implementations to operate on all forms of JSON without worrying about the implementation.

A reader is a forward cursor over that sequence. It starts on ast_node_type::document_start, so the first thing to do is step onto the value itself – which jsonv::extract does for you when handed a fresh reader. Reading an object means walking its keys, handling the ones you recognize and skipping the ones you do not:

struct my_object
{
std::int64_t a = 0;
};
std::optional<my_object> extract_my_object(jsonv::reader& from)
{
if (!from.expect(jsonv::ast_node_type::object_begin))
return std::nullopt;
// Step onto the first key, or onto the } of an empty object.
if (!from.next_token())
return std::nullopt;
my_object out;
while (from.good() && from.current_type() != jsonv::ast_node_type::object_end)
{
// Keys arrive canonical or escaped, depending on whether the source used escape sequences.
if (!from.expect({ jsonv::ast_node_type::key_canonical, jsonv::ast_node_type::key_escaped }))
return std::nullopt;
auto key = from.current().visit_key([](const auto& k) { return std::string(k.value()); });
if (key == "a")
{
if (!from.next_token())
return std::nullopt;
if (auto node = from.current_as<jsonv::ast_node::integer>())
out.a = node->value();
else
return std::nullopt;
// Step off the value and onto the next key, or onto the closing }.
if (!from.next_token())
return std::nullopt;
}
else
{
// A key we do not care about -- skip its value, however large, and land on the next key.
if (!from.next_key())
return std::nullopt;
}
}
return out;
}
auto visit_key(FVisitor &&visitor) const
Convenience function for calling std::visit on a key (see as_key).
Definition ast.hpp:496
A reader instance reads from some form of JSON source (probably a string) and converts it into a JSON...
Definition reader.hpp:104
ast_node_type current_type() const
Get the type of the current AST node, which is always current().type().
bool next_token() noexcept
Go to the next token.
std::expected< void, ast_node_type > expect(ast_node_type type) const
Check that the current AST node has the given type or is one of the expected types.
const ast_node & current() const
Get the current AST node this reader is pointing at.
bool next_key()
Go to the next object key or end-of-object.
std::expected< TAstNode, ast_node_type > current_as() const
Get the current AST node as a specific TAstNode subtype, calling expect beforehand.
Definition reader.hpp:224
bool good() const
Check if this reader is still good to read from.

Note that next_key is only valid while sitting on a key or on the opening { – it is the "skip this member" step, not the loop's advance. Use next_token to move off a value you have just read.

Definition at line 103 of file reader.hpp.

Constructor & Destructor Documentation

◆ reader() [1/7]

jsonv::reader::reader ( std::string_view  source)
explicit

Create a reader which reads from JSON source.

The source must stay in memory for the duration of this instance's use, unless it is an rvalue reference to a std::string, which is moved to the reader's implementation to keep it alive. It is parsed with parse_options where they are given, and with the defaults parse_index::parse uses where they are not.

◆ reader() [2/7]

jsonv::reader::reader ( std::string_view  source,
const parse_options &  parse_options 
)
explicit

Create a reader which reads from JSON source.

The source must stay in memory for the duration of this instance's use, unless it is an rvalue reference to a std::string, which is moved to the reader's implementation to keep it alive. It is parsed with parse_options where they are given, and with the defaults parse_index::parse uses where they are not.

◆ reader() [3/7]

jsonv::reader::reader ( const char *  source)
explicit

Create a reader which reads from JSON source.

The source must stay in memory for the duration of this instance's use, unless it is an rvalue reference to a std::string, which is moved to the reader's implementation to keep it alive. It is parsed with parse_options where they are given, and with the defaults parse_index::parse uses where they are not.

◆ reader() [4/7]

jsonv::reader::reader ( const char *  source,
const parse_options &  parse_options 
)
explicit

Create a reader which reads from JSON source.

The source must stay in memory for the duration of this instance's use, unless it is an rvalue reference to a std::string, which is moved to the reader's implementation to keep it alive. It is parsed with parse_options where they are given, and with the defaults parse_index::parse uses where they are not.

◆ reader() [5/7]

jsonv::reader::reader ( std::string &&  source)
explicit

Create a reader which reads from JSON source.

The source must stay in memory for the duration of this instance's use, unless it is an rvalue reference to a std::string, which is moved to the reader's implementation to keep it alive. It is parsed with parse_options where they are given, and with the defaults parse_index::parse uses where they are not.

◆ reader() [6/7]

jsonv::reader::reader ( std::string &&  source,
const parse_options &  parse_options 
)
explicit

Create a reader which reads from JSON source.

The source must stay in memory for the duration of this instance's use, unless it is an rvalue reference to a std::string, which is moved to the reader's implementation to keep it alive. It is parsed with parse_options where they are given, and with the defaults parse_index::parse uses where they are not.

◆ reader() [7/7]

jsonv::reader::reader ( reader &&  )
noexcept

Moving a reader transfers its implementation, leaving the source moved-from: good is false and the accessors throw std::invalid_argument.

These are out-of-line because destroying the implementation needs a complete reader::impl, which this header does not have – the same reason the destructor is.

Member Function Documentation

◆ current()

const ast_node & jsonv::reader::current ( ) const

Get the current AST node this reader is pointing at.

Exceptions
std::logic_errorif this instance is not good, or std::invalid_argument if it has been moved-from.

◆ current_as()

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

Get the current AST node as a specific TAstNode subtype, calling expect beforehand.

Returns
The current node as a TAstNode; otherwise the ast_node_type the current node actually has.
Exceptions
std::logic_errorif this instance is not good, or std::invalid_argument if it has been moved-from.

Definition at line 224 of file reader.hpp.

◆ current_path()

const path & jsonv::reader::current_path ( ) const

Get the path to the current node this reader is pointing at.

This is used in the generation of error messages to describe the location of something that could not be extracted.

^ /* "." -- start of document is the empty path */
{ /* "." -- opening { is still an empty path */
"a": /* ".a" -- the key starts the path */
[ /* ".a" -- the path refers to the entire array */
1, /* ".a[0]" */
2, /* ".a[1]" */
3, /* ".a[2]" */
], /* ".a" -- the path at the end of the array refers to the entire array again */
"b": /* ".b" */
{ /* ".b" -- the path refers to the entire object */
"x": /* ".b.x" */
"taco" /* ".b.x" */
}, /* ".b" */
"c": /* ".c" */
4 /* ".c" */
} /* "." */
$ /* "." */
Exceptions
std::logic_errorif this instance is not good, or std::invalid_argument if it has been moved-from.

◆ current_type()

ast_node_type jsonv::reader::current_type ( ) const

Get the type of the current AST node, which is always current().type().

Use this where the type is all that is wanted – checking for the ] which ends an array, say. A reader over a value has to synthesise token text for a number, a string or a key before it can hand out an ast_node, and answering this does not.

Exceptions
std::logic_errorif this instance is not good, or std::invalid_argument if it has been moved-from.

◆ current_value()

optional< const value & > jsonv::reader::current_value ( ) const
noexcept

Get the in-memory value this reader is positioned on, if it has one to lend.

A reader created by from_value is walking a value which already exists, so the subtree under current is something it can hand out by reference rather than rebuild. A reader over JSON text has no such tree.

Extraction uses this to avoid copying a subtree it was already given, and so that an extractor which returns a view of what it was handed – std::string_view among them – borrows the caller's storage rather than a temporary which dies with the call.

Returns
The value current names; or nothing if this reader is not value-backed, is not good, or is positioned somewhere which does not start a value, such as an object key or a closing token.

◆ expect() [1/2]

std::expected< void, ast_node_type > jsonv::reader::expect ( ast_node_type  type) const

Check that the current AST node has the given type or is one of the expected types.

Returns
Nothing if the current node matches type or one of the given types; otherwise the ast_node_type the current node actually has.
Exceptions
std::invalid_argumentif types is empty.
std::logic_errorif this instance is not good, or std::invalid_argument if it has been moved-from.
See also
ast_node::expect

◆ expect() [2/2]

std::expected< void, ast_node_type > jsonv::reader::expect ( std::initializer_list< ast_node_type >  types) const

Check that the current AST node has the given type or is one of the expected types.

Returns
Nothing if the current node matches type or one of the given types; otherwise the ast_node_type the current node actually has.
Exceptions
std::invalid_argumentif types is empty.
std::logic_errorif this instance is not good, or std::invalid_argument if it has been moved-from.
See also
ast_node::expect

◆ from_value() [1/2]

static reader jsonv::reader::from_value ( const value &  value)
static

Create a reader which reads from an in-memory value.

Parameters
valueThe value to read from. The overload taking a reference does not copy it, so it must remain valid for the lifetime of the reader; the rvalue overload moves value into the reader, which then keeps it alive.

These are named rather than constructors because value converts implicitly from std::string, among others. A reader(const value&) constructor would make reader(some_std_string) ambiguous – a std::string reaches std::string_view and value through user-defined conversions of equal rank – and the same trap would reopen for every type value grows a converting constructor from. Returning by value costs nothing: the result is a prvalue of the returned type, so it initializes the caller's object directly without a move.

◆ from_value() [2/2]

static reader jsonv::reader::from_value ( value &&  value)
static

Create a reader which reads from an in-memory value.

Parameters
valueThe value to read from. The overload taking a reference does not copy it, so it must remain valid for the lifetime of the reader; the rvalue overload moves value into the reader, which then keeps it alive.

These are named rather than constructors because value converts implicitly from std::string, among others. A reader(const value&) constructor would make reader(some_std_string) ambiguous – a std::string reaches std::string_view and value through user-defined conversions of equal rank – and the same trap would reopen for every type value grows a converting constructor from. Returning by value costs nothing: the result is a prvalue of the returned type, so it initializes the caller's object directly without a move.

◆ good()

bool jsonv::reader::good ( ) const

Check if this reader is still good to read from.

This will be true if this instance has not been moved-from and has not reached EOF. If this is false, current or current_path will throw an exception.

◆ next_key()

bool jsonv::reader::next_key ( )

Go to the next object key or end-of-object.

^
{
"a": /* <- calling here goes to "b" */
[ /* <- calling when not on an object key throws */
1,
2,
3,
],
"b": /* <- calling here goes to "c" */
{
},
"c": /* <- calling here goes to end of object */
4
}
$
Returns
true if the reader is still good to read from current.
Exceptions
std::invalid_argumentif the reader is not currently at the start of a key.

◆ next_structure()

bool jsonv::reader::next_structure ( )
noexcept

Go to one past the end of the current structure.

^
{
"a": /* <- go to end of document */
[ /* <- go to "b" */
1, /* <- go to "b", too */
2,
3,
], /* <- go to "b" */
"b": /* <- go to end of document */
{ /* <- go to "c" */
}, /* <- go to "c" */
"c":
4
} /* <- go to end of document */
$

The point of this over next_token is abandoning a structure you are only part-way through. Once the one member you came for has been read, there is no reason to walk the rest of the object:

/// Find the "a" member of an object and leave the rest of it unread.
std::optional<std::int64_t> find_a(jsonv::reader& from)
{
if (!from.expect(jsonv::ast_node_type::object_begin))
return std::nullopt;
if (!from.next_token())
return std::nullopt;
while (from.good() && from.current_type() != jsonv::ast_node_type::object_end)
{
if (!from.expect({ jsonv::ast_node_type::key_canonical, jsonv::ast_node_type::key_escaped }))
return std::nullopt;
auto key = from.current().visit_key([](const auto& k) { return std::string(k.value()); });
if (key != "a")
{
if (!from.next_key())
return std::nullopt;
continue;
}
if (!from.next_token())
return std::nullopt;
auto node = from.current_as<jsonv::ast_node::integer>();
// Whatever is left of this object, we are done with it.
(void) from.next_structure();
if (node)
return node->value();
else
return std::nullopt;
}
return std::nullopt;
}
bool next_structure() noexcept
Go to one past the end of the current structure.

Note the call site: next_structure is used while sitting on a value inside the object, which is where it differs from next_token. Called on the closing } itself it is merely next_token, since there is no longer a structure to leave.

Returns
true if the reader is still good to read from current.

◆ next_token()

bool jsonv::reader::next_token ( )
noexcept

Go to the next token.

^
{ /* <- go to "a" */
"a": /* <- go to [ */
[ /* <- go to 1 */
1, /* <- go to 2 */
2, /* ...and so on */
3,
],
"b":
{
},
"c":
4
}
$
Returns
true if the reader is still good to read from current.

◆ next_value()

bool jsonv::reader::next_value ( )
noexcept

Go to one past the value this reader is on.

Unlike next_structure, which leaves the structure the reader is inside, this steps over the single value the reader is on. On a structure that means the token after its matching close, since the structure is the value being stepped over; on anything else it is the same as next_token.

^
{
"a": /* <- go to [ */
[ /* <- go to "b" */
1, /* <- go to 2 */
2,
3,
],
"b":
{ /* <- go to "c" */
},
"c":
4 /* <- go to } */
}
$

This is the primitive for ignoring a value you do not want. Note the difference from next_structure at the 4 above: this goes to the }, while next_structure leaves the enclosing object entirely.

Returns
true if the reader is still good to read from current.

◆ operator=()

reader & jsonv::reader::operator= ( reader &&  )
noexcept

Moving a reader transfers its implementation, leaving the source moved-from: good is false and the accessors throw std::invalid_argument.

These are out-of-line because destroying the implementation needs a complete reader::impl, which this header does not have – the same reason the destructor is.

◆ owns_source()

bool jsonv::reader::owns_source ( ) const
noexcept

Does this reader own the storage it reads from?

This is true for a reader created from a std::string rvalue or by from_value(value&&), which keep their source alive for exactly as long as the reader. Anything extracted as a view of the source – a std::string_view, say – is then valid only while the reader is. It is false for every other source, where the caller owns the storage, and for a moved-from reader.

◆ validate()

void jsonv::reader::validate ( ) const

Check that the source this reader was created from is valid JSON.

Parsing text never throws: a malformed document produces a sequence which stops at an ast_node_type::error 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 act on, so this is the question parse_index::validate answers, asked of the reader. It is about the source, not the cursor, so the answer is the same wherever the reader is positioned.

Exceptions
parse_errorif this reader is over JSON text which did not parse. A reader over a value has nothing to parse and never throws this.
std::invalid_argumentif this instance has been moved-from.

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