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

Detailed Description

Provide an in-memory representation of a JSON document.

Outline

Purpose

Provide an in-memory representation of a JSON document.

Classes

See also
bdljsn_jsonutil, bdljsn_jsonnumber, bdljsn_jsonnull

Description

This component provides a value-semantic type, bdljsn::Json, used as an in-memory representation for a JSON document. This component also provides bdljsn::JsonArray and bdljsn::JsonObject types for representing JSON Arrays and Objects respectively.

bdljsn::Json has a close structural similarity to the JSON grammar itself, which at a high-level looks like:

JSON ::= Object
| Array
| String
| Number
| Boolean
| null

Where the Object and Array alternatives can recursively contain JSON. Just like this grammar, a bdljsn::Json is a variant holding either an Object, Array, String, Number, Boolean, or null. These variant selections are represented by the following types:

For more details on the JSON grammar see:

Reading and Writing a bdljsn::Json Object

bdljsn_jsonutil is the recommended facility to write and read bdljsn::Json objects to and from JSON document text.

operator<< overloads are available for all the types. In addition a canonical BDE print method is available for bdljsn::Json, bdljsn::JsonObject, bdljsn::JsonArray bdljsn::JsonNumber and bdljsn::JsonNull.

All bdljsn::Json Objects are Valid JSON Documents

Every bdljsn::Json represents a (valid) JSON document (bdljsn::Json does not have an "invalid" state). This means writing a bdljsn::Json to a string can only fail if an out-of-memory condition occurs.

Similarly, every JSON document has a bdljsn::Json representation. However, bdljsn::JsonObject represents Objects having only unique member names, meaning that duplicate member names in a JSON document will be ignored (see bdljsn::JsonUtil for more information on how duplicate names are handled when parsing a JSON document). Note that the JSON RFC says that Object member names "SHOULD be unique", and there is no standard behavior for JSON parsers where member names are not unique (typically an in-process representation with unique member names is used).

Important Preconditions

In order to preserve the invariant that all bdljsn::Json objects are valid JSON documents there are some constructors and assignment operations in bdljsn package that have notable preconditions:

operator== and the Definition of Value

bdljsn::Json type's definition of value (i.e., the behavior for operator==) mirrors comparing the text of two JSON documents where all the white-space is ignored.

Concretely, bdljsn::Json is a variant type, whose definition of equality is derived from the definition of equality of its constituent types. I.e., two bdljsn::Json objects compare equal if they have the same type and the two objects of that type they contain compare equal. The definition of equality for Object, Array, Boolean, string, and JsonNull types are relatively self-explanatory (see respective operator== definitions for details). The definition of equality for JsonNumber is notable: JsonNumber objects define value in terms of the text of the JSON number string they contain. So two JSON numbers having the same numerical value may compare unequal (e.g., "2" and "2.0" and "20e-1" are considered different JsonNumber values!). Note that bdljsn::JsonNumber::isEqual provides a semantic comparison of two numbers (see bdljsn_jsonnumber for more detail).

Initializer Lists

The bdljsn::JsonArray and bdljsn::JsonObject classes have constructors and assorted manipulator methods (e.g., assign, insert) that accept instances of std::initializer_list which are invoked when users supply braced lists of values. When values of the following types are used in those braced lists, the values are converted to bdljsn::Json objects (as the JsonArray elements or as the "value" portion of JsonObject members).

Usage

This section illustrates the intended use of this component.

Example 1: Constructor a Basic bdljsn::Json Object

Most often bdljsn::Json objects will be written and read from JSON text using bdljsn_jsonutil . In this simple example, we demonstrate manually creating the document below and then verify the properties of the resulting object:

{
"number": 3.14,
"boolean": true,
"string": "text",
"null": null,
"array": [ "2.76", true ],
"object": { "boolean": false }
}

First, we use bdljsn::Json::makeObject to configure the top level bdljsn::Json object to be a bdljsn::Json object, and use the various manipulators of bdljsn::JsonObject to configure its value:

using namespace bdldfp::DecimalLiterals;
json.makeObject();
json["number"] = 3.14;
json["boolean"] = true;
json["string"] = "text";
json["array"].makeArray();
json["array"].theArray().pushBack(bdljsn::Json(2.76_d64));
json["array"].theArray().pushBack(bdljsn::Json(true));
json["object"].makeObject()["boolean"] = false;
JsonArray & pushBack(const Json &json)
Definition bdljsn_json.h:3561
Definition bdljsn_json.h:1461
JsonArray & theArray()
Definition bdljsn_json.h:5094
JsonObject & makeObject()
Definition bdljsn_json.h:4531
JsonArray & makeArray()
Definition bdljsn_json.h:4472

Notice that we used operator[] to implicitly create new members of the top-level object. Using json.theObject().insert would be more efficient (see example 2).

Finally, we validate the properties of the resulting object:

assert(3.14 == json["number"].asDouble());
assert(true == json["boolean"].theBoolean());
assert("text" == json["string"].theString());
assert(true == json["null"].isNull());
assert(2.76_d64 == json["array"][0].asDecimal64());
assert(false == json["object"]["boolean"].theBoolean());

Example 2: More Efficiently Creating a bdljsn::Json

Example 1 used operator[] to implicitly add members to the Objects. Using operator[] is intuitive but not the most efficient method to add new members to a bdljsn::JsonObject (similar to using operator[] to add elements to an unordered_map). The following code demonstrates a more efficient way to create the same bdljsn::Json representation as Example 1:

using namespace bdldfp::DecimalLiterals;
json.makeObject();
json.theObject().insert("number", bdljsn::JsonNumber(3.14));
json.theObject().insert("boolean", true);
json.theObject().insert("string", "text");
json.theObject().insert("null", bdljsn::JsonNull());
subArray.pushBack(bdljsn::Json(2.76_d64));
subArray.pushBack(bdljsn::Json(true));
json.theObject().insert("array", bsl::move(subArray));
subObject.insert("boolean", false);
json.theObject().insert("object", bsl::move(subObject));
Definition bdljsn_json.h:551
Definition bdljsn_jsonnull.h:121
Definition bdljsn_jsonnumber.h:412
Definition bdljsn_json.h:1080
bsl::pair< Iterator, bool > insert(const Member &member)
Definition bdljsn_json.h:3992
JsonObject & theObject()
Definition bdljsn_json.h:5118

Example 3: Using the visit Method to Traverse a Json Object

The Json class provides the (overloaded) visit method that invokes a user-supplied "visitor" functor according to the current type() of the Json object.

For example, suppose one needs to survey the structure of the Json object created in {Example 1} (and again in {Example 2}) and, in doing so, compile a tally of each of each of the Json sub-objects and their their types (i.e., object, array, string, ...).

First, we define a compliant visitor class, TallyByTypeVisitor:

// ==================
// TallyByTypeVisitor
// ==================
class TallyByTypeVisitor {
int d_tally[6];
public:
// CREATORS
/// Create a `TallyByTypeVisitor` object that when passed to the
/// `Json::visit` method will increment the specified `tally` array
/// according to `type()` and visit subordinate `Json` objects, if
/// any. The behavior is undefined unless `tally` has at least
/// 6 elements.
explicit TallyByTypeVisitor();
// ACCESSORS
/// Increment the element corresponding to object in the tally array
/// supplied at construction and visit the value of each member of
/// the specified `object`.
void operator()(const JsonObject& object);
/// Increment the element corresponding to array in the tally array
/// supplied at construction and visit each element of the specified
/// `array`.
void operator()(const JsonArray& array);
/// Increment the element corresponding to
/// string/number/boolean/null in the tally array supplied at
/// construction. Ignore the specified
/// `string`/`number`/`boolean`/`null`.
void operator()(const bsl::string& string );
void operator()(const JsonNumber& number );
void operator()(const bool& boolean);
void operator()(const JsonNull& null );
/// Return the address of an array of 6 elements containing the
/// tally by type. The array is ordered according to
/// `JsonType::Enum`.
const int *tally() const;
};
Definition bslstl_string.h:1252

Notice that we have no need to change the value of the examined Json objects so we use a set of operator() overloads compatible with the visit accessor method. Accordingly, we careful below to use this visitor only with const-qualified Json objects

Then, we define the constructor and the six operator() overloads. In each overload by type we increment the appropriate element in the user-supplied array of integers.

// ------------------
// TallyByTypeVisitor
// ------------------
// CREATORS
TallyByTypeVisitor::TallyByTypeVisitor()
{
d_tally[bdljsn::JsonType::e_NULL ] = 0;
}
// ACCESSORS
void TallyByTypeVisitor::operator()(const bsl::string& string)
{
(void) string;
}
void TallyByTypeVisitor::operator()(const JsonNumber& number)
{
(void) number;
}
void TallyByTypeVisitor::operator()(const bool& boolean)
{
(void) boolean;
}
void TallyByTypeVisitor::operator()(const JsonNull& null)
{
(void) null;
}
@ e_STRING
Definition bdljsn_jsontype.h:132
@ e_BOOLEAN
Definition bdljsn_jsontype.h:134
@ e_ARRAY
Definition bdljsn_jsontype.h:131
@ e_NULL
Definition bdljsn_jsontype.h:135
@ e_OBJECT
Definition bdljsn_jsontype.h:130
@ e_NUMBER
Definition bdljsn_jsontype.h:133

Next, we define the visitor overload for array types. After incrementing the tally array we pass this same visitor object to the Json object in the array so those objects are included in the tally.

void TallyByTypeVisitor::operator()(const JsonArray& array)
{
typedef JsonArray::ConstIterator ConstItr;
for (ConstItr cur = array.cbegin(),
end = array.cend();
end != cur; ++cur) {
const Json constElement = *cur;
constElement.visit<void>(*this);
}
}
const int *TallyByTypeVisitor::tally() const
{
return d_tally;
}

Notice that element is const-qualified so the accessor visit method is invoked.

Then, we implement the visitor overload for the object type. Examination of the object produces a sequence of name-value pairs where the second part is a Json object that we must visit.

void TallyByTypeVisitor::operator()(const JsonObject& object)
{
typedef JsonObject::ConstIterator ConstItr;
for (ConstItr cur = object.cbegin(),
end = object.cend();
end != cur; ++cur) {
const Json& json = member.second;
json.visit<void>(*this);
}
}
Definition bslstl_pair.h:1280
TYPE second
Definition bslstl_pair.h:933

Again, notice that this visitor is used as an argument to a const-qualified Json object.

Finally, we make a survey of the Json object created in {Example 1} (and duplicated in {Example 2}). From visual inspection of the source JSON document we expect 10 Json objects distributed thus:

Use of our visitor functor on example1 confirms these observations:

int main()
{
TallyByTypeVisitor visitor;
const Json& constExample1 = example1;
constExample1.visit<void>(&visitor);
const int *const tally = visitor.tally();
assert(2 == tally[bdljsn::JsonType::e_OBJECT ]);
assert(1 == tally[bdljsn::JsonType::e_ARRAY ]);
assert(1 == tally[bdljsn::JsonType::e_STRING ]);
assert(2 == tally[bdljsn::JsonType::e_NUMBER ]);
assert(3 == tally[bdljsn::JsonType::e_BOOLEAN]);
assert(1 == tally[bdljsn::JsonType::e_NULL ]);
return 0;
}