Home | Libraries | People | FAQ | More |
Trial.Protocol can process JSON incrementally or via serialization. Incremental processing handles JSON one token at the time, whereas serialization processes the entire JSON buffer in a single operation. A token is a single element in the JSON grammar, such as a number, a string, the beginning or the end of an array.
Incremental processing has a lower-level interface than serialization or document processing, but even though incremental processing is more laborious to use, it covers more use cases and can be used more efficiently. Some examples of these use cases are:
Incremental processing is a low-level API which regards JSON as a sequence of tokens to be processed one by one.
The JSON parser and generator use tokens to identify data types as well
as errors. All token-related types are located in the trial::protocol::json::token
namespace, which we will simply refer to as token
below.
Note | |
---|---|
Tokens are located in the |
A token is represented by the token::code
enumeration type with a constant for each possible token or error state.
This means that each error is represented by its own enumerator constant.
Working directly with token::code
can be tedious. Suppose you want to check if an error occurred, then
you have to check if the current token is one of the numerous error constants.
Each token::code
enumerator has therefore been
grouped into a more convenient enumeration type called token::symbol
that is better suited for normal
operation.
All token::code
error constants have been grouped
into the single token::symbol::error
constant, and we can now check for errors with a single comparison.
Table 1.1. JSON symbol constants
|
Description |
---|---|
|
True or false. |
|
Integer number. |
|
Floating-point number. |
|
String value. |
|
No data. |
|
Start of an array. |
|
End of an array. |
|
Start of an associative array. |
|
End of an associative array. |
|
A context-specific separator. |
|
End of input or output buffer. |
|
Erroneous format. |
The symbol type will be the preferred manner to use tokens in the examples
throughout this documentation. In fact, we are not even going to describe
the token::code
enumerator constants here,[6] because we are only interested in the subset that contains
the error codes and they are described in the section on errors.
The symbol constants are grouped into the token::category
enumeration type. There are different categories of tokens:
Table 1.2. JSON category constants
|
Description |
---|---|
|
Data tokens have a value associated with them, whose content can be retrieved. Examples of data tokens are booleans, numbers, and strings. |
|
Structural tokens wrap containers and separate items. |
|
The nullable token is a special case, because it can represent either a data token without and associated value or structural token without an associated container, such as a missing integer or a missing array. The nullable token is typeless. |
|
A status token indicates another condition. |
The following table shows which categories the the various symbols belong to.
Table 1.3. Relation between symbols and categories
|
|
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The <system_error>
framework is used for error
codes and exceptions.
Note | |
---|---|
Error codes and utilities are located in the |
Trial.Protocol defines its own json::error_category
with associated error enumerator constants.
Normally, an error_code
of the current error can be obtained via an error()
member function.
std::string input = "illegal"; json::reader reader(input); assert(reader.symbol() == json::symbol::error); assert(reader.error() == json::invalid_value);
This conversion can also be done manually with json::to_errc()
and json::make_error_code()
.
std::string input = "illegal"; json::reader reader(input); assert(reader.symbol() == json::symbol::error); assert(reader.code() == json::code::error_invalid_value); enum json::errc ec = json::to_errc(reader.code()); assert(ec == json::invalid_value); auto error = json::make_error_code(ec); assert(error == json::invalid_value);
The following error codes exists.
Table 1.4. Error codes
|
Description |
---|---|
|
An unexpected token is encountered in the input. |
|
An associative array key is not in a valid format. |
|
The content is not in a valid format. |
|
Conversion between two incompatible types failed. |
|
Encountered an end array token without a corresponding begin array token. |
|
Encountered an end object token without a corresponding begin object token. |
|
Encountered an end array token outside an array. |
|
Encountered an end object token outside an associative array. |
Conversion errors will result in json::error
exceptions being thrown. json::error
inherits from std::system_error
which contains a std::error_code
.
Reader is an incremental parser (also called a pull parser) that transforms the JSON input into a sequence of C++ tokens. This transformation is done piecemeal, which means that the reader will stay at the first token until explicitly instructed to parse the next token.
Based on the Iterator
design pattern, reader
parses just enough of the input to identify a single token. The reader
provides various accessors that
can be used to examine or convert the current token.
Table 1.5. Reader Accessors
Reader member function |
Description |
---|---|
|
Returns the current token. |
|
Returns the symbol of the current token. |
|
Returns the category of the current token. |
|
Returns the current level of nested containers. The levels starts with zero for the outmost level. |
|
Returns the current error code. |
|
Returns a view of the raw input of the current value. |
|
Returns the current value. The raw input is converted into the requested value type. |
No data is converted until explicitly requested with reader::value<T>()
. Notice that the return type for
reader::value<T>()
must be specified as a template parameter. The return type can be a boolean,
a number, or a string as described below. The requested type must match
the current token as returned by reader::type()
; otherwise a run-time error will be
raised.
Note | |
---|---|
Requesting the value of an incompatible type will result in a run-time error. For example, attempting to read a string as an integer: assert(reader.symbol() == json::symbol::string); int number = reader.value<int>(); // Throws exception
|
Note | |
---|---|
Requesting an unsupported type will result in a compile-time error. For example, attempting to read a user-defined struct: struct dummy {}; dummy d = reader.value<dummy>(); // Causes compilation error
|
The unconverted textual data of the current token can be obtained with
reader::literal()
.
This can be useful when displaying errors. The result of reader::literal()
is different from reader::value<std::string>()
.
When you are done with the current token, the next token is found with
reader::next()
.
This function returns a bool, which is true unless either an error or
the end of the input was encountered. Whitespaces and separators are
skipped.
Errors in the input are identified with an error token, and the current
error can be obtained with reader::error()
.
Boolean values are indicated by the json::symbol::boolean
token. The value is requested by reader::value<bool>()
.
std::string input = "true"; json::reader reader(input); assert(reader.symbol() == json::symbol::boolean); assert(reader.literal() == "true"); assert(reader.value<bool>());
While JSON does not distinguish between integer and floating-point numbers, C++ does make this distinction and therefore Trial.Protocol does too. However, integers can be read as floating-point numbers, and floating-point numbers can be read as integers.
Note | |
---|---|
Reading a floating-point number as an integer will round the number, so it may result in loss of information. |
Numbers are identified as integers if the consists of digits only.
std::string input = "42"; json::reader reader(input); assert(reader.symbol() == json::symbol::integer); assert(reader.literal() == "42"); assert(reader.value<int>() == 42);
Numbers are detected as floating-point if they contain a decimal point or an exponent.
std::string input = "3.1415"; json::reader reader(input); assert(reader.symbol() == json::symbol::number); assert(reader.literal() == "3.1415"); assert(reader.value<double>() == 3.1415);
Integers can be read as floating-point numbers as well.
std::string input = "42"; json::reader reader(input); assert(reader.symbol() == json::symbol::integer); assert(reader.value<double>() == 42.0);
Floating-point numbers can also be read as integers. The number will be rounded to the nearest integer.
std::string input = "3.1415"; json::reader reader(input); assert(reader.symbol() == json::symbol::number); assert(reader.value<int>() == 3);
Any kind of additional constraints have to be enforced by the application layer. For instance, if we have a protocol with a size field, then logically this field cannot be negative or a fraction, even if JSON numbers allow this.
Strings are identified with the json::symbol::string
token, and is converted into a UTF-8 encoded string with reader::value<std::string>()
.
std::string input = "\"alpha\\n\""; json::reader reader(input); assert(reader.symbol() == json::symbol::string); assert(reader.literal() == "\"alpha\\n\""); assert(reader.value<std::string>() == "alpha\n");
Null indicates the absence of a value, although it is encoded explicitly
in the JSON format as the null
literal string.
std::string input = "null"; json::reader reader(input); assert(reader.symbol() == json::symbol::null);
Arrays are delimited by a begin-token and an end-token. Array members are comma separated. Arrays can contain any JSON type, including a nested array or a nested associative array.
std::string input = "[42]"; json::reader reader(input); assert(reader.symbol() == json::symbol::begin_array); reader.next(); assert(reader.symbol() == json::symbol::integer); assert(reader.value<int>() == 42); reader.next(); assert(reader.symbol() == json::symbol::end_array);
An associative array is called a JSON object, which as a first approximation
can be thought of as a std::map
in C++.
Writer is an incremental generator that outputs C++ data types in a JSON
format. The output is generated piece by piece as C++ data types are
inserted. The writer
keeps track of the context and inserts the appropriate separators between
values where needed.
Table 1.6. Writer Accessors
Writer member function |
Description |
---|---|
|
Returns the current level of nested containers. |
|
Returns the current error code. |
|
Write a literal value directly into the JSON output without formatting it. Returns the number of characters written. Returns zero if an error occurred. |
|
Write a formatted tag into the JSON output. Returns the number of characters written. Returns zero if an error occurred. |
|
Write a formatted value into the JSON output. Returns the number of characters written. Returns zero if an error occurred. |
Values are properly formatted and written into the JSON output with
writer::value(T)
.
The parameter T
can be
a boolean, a number, or a string. Writing a nullable value or the opening
and closing brackets of containers is done by passing special tags as
the parameter to the parameter-less version writer::value<T>()
.
Literal values can also be inserted unconverted into the JSON output
with writer::literal()
.
These can be useful useful for adding whitespaces, but special care should
be exerted to not violate the JSON format.
As writer
has been designed
for wire protocols, it does not insert whitespaces into the output[7].
Note | |
---|---|
The following examples assume that you have included the following header files: #include <trial/protocol/buffer/ostream.hpp> #include <trial/protocol/json/writer.hpp>
|
Boolean values are output via writer::value(bool)
.
std::ostringstream result; json::writer writer(result); writer.value(true); // Write boolean value assert(result.str() == "true");
Numbers can either be integer values or floating-point values.
Strings are written by passing an std::string
or a string literal to writer::value(T)
. All strings will be quoted in the JSON
output, and special characters will be escaped. Strings must be UTF-8
encoded.
std::ostringstream result; json::writer writer(result); writer.value("alpha"); // Write string assert(result.str() == "\"alpha\"");
Nullable value are output with writer::value<token::null>()
.
std::ostringstream result; json::writer writer(result); writer.value<json::token::null>(); // Write nullable value assert(result.str() == "null");
An array is initiated by passing the json::begin_array
tag to writer::value(T)
,
and terminated by passing the json::end_array
.
These tags must be properly balanced, otherwise an error will be raised.
Value separators are automatically inserted between values.
std::ostringstream result; json::writer writer(result); writer.value(json::begin_array); // Write beginning of array assert(result.str() == "["); writer.value(42); assert(result.str() == "[42"); // Write number writer.value(43); assert(result.str() == "[42,43"); // Write number writer.value(json::end_array); assert(result.str() == "[42,43]"); // Write ending of array
Name separators are automatically inserted between the key and the value, and value separators are automatically inserted between key-value pairs.