BDE 4.39.x Production Release
Loading...
Searching...
No Matches
bdljsn_jsonutil

Detailed Description

Provide common non-primitive operations on Json objects.

Outline

Purpose

Provide common non-primitive operations on Json objects.

Classes

Description

This component provides a namespace bdljsn::JsonUtil containing utility functions that operate on Json objects.

The following methods are provided by JsonUtil:

Configuring the Output Format

There are a number of options to configure the output format produced by write:

The example below shows the various write styles:

Compact:
{"a":1,"b":[]}
One-line:
{"a": 1, "b": []}
Pretty:
{
"a": 1,
"b": []
}

For more information, see the bdljsn_writeoptions and bdljsn_writestyle components.

Handling of Duplicate Keys

bdljsn::JsonObject represents a JSON Object having unique keys. If an Object with duplicate keys is found in a JSON document, read will preserve the value associated with the FIRST instance of that key.

Per the JSON RFC (https://www.rfc-editor.org/rfc/rfc8259#section-4):

"The names within an object SHOULD be unique."

That is, the expectation is that a JSON document should have unique keys, and JSON documents with duplicate keys are not an interoperable representation. JSON parsing implementations vary on how duplicate keys are handled (though many represent the object in-memory with unique keys). Note that preserving the value of the first key is consistent with the behavior of the existing baljsn::DatumUtil component.

Allowing Trailing Text

By default, bdljsn::JsonUtil::read will report an error for input where a valid JSON document is followed by additional text unless the trailing text consists solely of white space characters. This behavior is configured by the bdljsn::ReadOptions attribute, "allowTrailingText" (which defaults to false).

If "allowTrailingText" is true, then bdljsn::JsonUtil::read will return success where a valid JSON document is followed by additional text as long as that text is separated from the valid JSON by a delimiter character (i.e., either the JSON text ends in a delimiter, or the text that follows starts with a delimiter). Here, delimiters are white-space characters, [, ], {, }, ,, or double-quotes. Per RFC 8259, white space characters are Space (0x20), Horizontal tab (0x09), New Line (0x0A), and Carriage Return (0x0D).

The table below shows some examples:

* "ATT" = "allowTrailingText"
* Document is only valid where the result is SUCCESS
+-----------+------------------------+-------------+-----------+
| Input | ATT = false (default) | ATT = true | Document |
+===========+========================+=============+===========+
| '[]' | SUCCESS | SUCCESS | [] |
| '[] ' | SUCCESS | SUCCESS | [] |
| '[],' | ERROR | SUCCESS | [] |
| '[]a' | ERROR | SUCCESS | [] |
| 'false ' | SUCCESS | SUCCESS | false |
| 'false,' | ERROR | SUCCESS | false |
| 'falsea' | ERROR | ERROR | |
| '"a"x' | ERROR | SUCCESS | "a" |
+-----------+------------------------+-------------+-----------+

Usage

This section illustrates the intended use of this component.

Example 1: Reading and Writing JSON Data

This component provides methods for reading and writing JSON data to/from Json objects.

First, we define the JSON data we plan to read:

const char *INPUT_JSON = R"JSON({
"a boolean": true,
"a date": "1970-01-01",
"a number": 2.1,
"an integer": 10,
"array of values": [
-1,
0,
2.718281828459045,
3.1415926535979,
"abc",
true
],
"event": {
"date": "1969-07-16",
"description": "Apollo 11 Moon Landing",
"passengers": [
"Neil Armstrong",
"Buzz Aldrin"
],
"success": true,
"target": "/luna/sea-of-tranquility"
}
}})JSON";

Next, we read the JSON data into a Json object:

bdljsn::Json result;
int rc = bdljsn::JsonUtil::read(&result, &error, INPUT_JSON);
assert(0 == rc);
if (0 != rc) {
bsl::cout << "Error message: \"" << error.message() << "\""
<< bsl::endl;
}
Definition bdljsn_error.h:155
const bsl::string & message() const
Return the message attribute of this object.
Definition bdljsn_error.h:437
Definition bdljsn_json.h:1461
static int read(Json *result, bsl::istream &input)
Definition bdljsn_jsonutil.h:544

Then, we check the values of a few selected fields:

assert(result.type() == JsonType::e_OBJECT);
assert(result["array of values"][2].theNumber().asDouble()
== 2.718281828459045);
assert(result["event"]["date"].theString() == "1969-07-16");
assert(result["event"]["passengers"][1].theString() == "Buzz Aldrin");
JsonType::Enum type() const BSLS_KEYWORD_NOEXCEPT
Return the type of this Json value.
Definition bdljsn_json.h:5326

Finally, we'll write the result back into another string and make sure we got the same value back, by using the correct WriteOptions to match the input format:

bsl::string resultString;
// Set the `WriteOptions` to match the `INPUT_JSON` format:
WriteOptions writeOptions;
writeOptions.setEscapeForwardSlash(false);
writeOptions.setStyle(bdljsn::WriteStyle::e_PRETTY);
writeOptions.setInitialIndentLevel(0);
writeOptions.setSpacesPerLevel(2);
writeOptions.setSortMembers(true);
bdljsn::JsonUtil::write(&resultString, result, writeOptions);
assert(resultString == INPUT_JSON);
// Set the `WriteOptions` to escape forward slashes (for example, to avoid
// XSS issues in HTML contexts):
writeOptions.setEscapeForwardSlash(true);
bdljsn::JsonUtil::write(&resultString, result, writeOptions);
resultString.find("\"\\/moon\\/sea-of-tranquility\""));
resultString.find("\"/moon/sea-of-tranquility\""));
Definition bslstl_string.h:1252
size_type find(const basic_string &substring, size_type position=0) const BSLS_KEYWORD_NOEXCEPT
Definition bslstl_string.h:7432
static const size_type npos
Definition bslstl_string.h:1793
static int write(bsl::ostream &output, const Json &json)
Definition bdljsn_jsonutil.h:763
@ e_PRETTY
Definition bdljsn_writestyle.h:130

Example 2: The Effect of options on write

By populating a WriteOptions object and passing it to write, the format of the resulting JSON can be controlled.

First, let's populate a Json object named json from an input string using read, and create an empty options (see bdljsn::WriteOptions):

const bsl::string JSON = R"JSON(
{
"a" : 1,
"b" : []
}
)JSON";
int rc = bdljsn::JsonUtil::read(&json, JSON);
assert(0 == rc);
Definition bdljsn_writeoptions.h:138

There are 4 options, which can be broken down into 2 unrelated sets.

The first set consists of the sortMembers option, which controls whether members of objects are printed in lexicographical order.

The second set consists of the style, initialIndentLevel, and spacesPerLevel options - style controls the format used to render a Json, and, if bdljsn::WriteStyle::e_PRETTY == options.style(), the spacesPerLevel and initialIndentLevel options are used to control the indentation of the output. For any other value of options.style(), the spacesPerLevel and initialIndentLevel options have no effect.

sortMembers

If sortMembers is true, then the members of an object output by write will be in sorted order. Otherwise, the elements are written in an (implementation defined) order (that may change).

The sortMembers option defaults to false for performance reasons, but applications that rely on stable output text should set sortMembers to true (e.g., in a test where the resulting JSON text is compared for equality) .

Here, we set sortMembers to true, and verify the resulting JSON text matches the expected text:

options.setSortMembers(true);
bsl::string output;
rc = bdljsn::JsonUtil::write(&output, json, options);
assert(0 == rc);
assert(R"JSON({"a":1,"b":[]})JSON" == output);
WriteOptions & setSortMembers(bool value)
Definition bdljsn_writeoptions.h:337

Had we not specified setSortMembers(true), the order of the "a" and "b" members in the output string would be unpredictable.

style And style-related options

There are 3 options for style (see bdljsn::WriteStyle):

Next, we write json using the style e_COMPACT (the default), a single line presentation with no added spaces after : and , elements.

rc = bdljsn::JsonUtil::write(&output, json, options);
assert(0 == rc);
// Using 'e_COMPACT' style:
assert(R"JSON({"a":1,"b":[]})JSON" == output);

Next, we write json using the e_ONELINE style, another single line format, which adds single characters after : and , elements for readability.

rc = bdljsn::JsonUtil::write(&output, json, options);
assert(0 == rc);
// Using 'e_ONELINE' style:
assert(R"JSON({"a": 1, "b": []})JSON" == output);
WriteOptions & setStyle(bdljsn::WriteStyle::Enum value)
Definition bdljsn_writeoptions.h:353
@ e_ONELINE
Definition bdljsn_writestyle.h:131

Next, we write json using the e_PRETTY style, a multiline format where newlines are introduced after each (non-terminal) {, [, ,, ], and } character. Furthermore, the indentation of JSON rendered in the e_PRETTY style is controlled by the other 2 attributes, spacesPerLevel and initialIndentLevel.

e_PRETTY styling does not add a newline to the end of the output.

spacesPerLevel controls the number of spaces added for each successive indentation level - e.g., if spacesPerLevel is 2, then each nesting level of the rendered JSON is indented 2 spaces.

initialIndentLevel controls how much the entire JSON output is indented. It defaults to 0 - if it's a positive value, then the entire JSON is indented by initialIndentLevel * spacesPerLevel spaces.

options.setSpacesPerLevel(4); // the default
options.setInitialIndentLevel(0); // the default
rc = bdljsn::JsonUtil::write(&output, json, options);
assert(0 == rc);
// Using 'e_PRETTY' style:
assert(R"JSON({
"a": 1,
"b": []
})JSON" == output);
WriteOptions & setSpacesPerLevel(int value)
Definition bdljsn_writeoptions.h:344
WriteOptions & setInitialIndentLevel(int value)
Definition bdljsn_writeoptions.h:328

Finally, if we set initialIndentLevel to 1, then an extra set of 4 spaces is prepended to each line, where 4 is the value of spacesPerLevel:

rc = bdljsn::JsonUtil::write(&output, json, options);
assert(0 == rc);
// Using 'e_PRETTY' style (with 'initialIndentLevel' as 1):
assert(R"JSON({
"a": 1,
"b": []
})JSON" == output);