BDE 4.14.0 Production release
Loading...
Searching...
No Matches
baljsn_datumutil

Detailed Description

Outline

Purpose

Provide utilities converting between bdld::Datum and JSON data.

Classes

Description

This component provides a struct, baljsn::DatumUtil, that is a namespace for a suite of functions that convert a bdld::Datum into a JSON string, and back.

Mapping Data Types between Datum and JSON

While most scalar types supported by Datum can be encoded into a JSON string, only the subset of types represented natively in JSON – number (represented in C++ as a double), string, bool, null, array, and map types – will be populated in a Datum decoded from a JSON string. If one were to encode a Datum containing a type not natively supported by JSON, if that JSON string were decoded back into a Datum object, the resulting Datum would not be equal to the original value. For example, a Datum containing an integer would be encoded into a JSON number, and then decoded back into a Datum using double to represent that number. Note that DatumUtil uses a more permissive parser for numerical values than the strict JSON standard specifies. In particular, it is possible to parse NaN, Inf, or Infinity into the corresponding singular double values even though the JSON standard does not permit this, so applications should be ready to handle these kinds of values. Also note that the encode routines do not encode these singular double values in these parseable formats. Singular double values will be rendered as strings (e.g., "+inf" or "nan") if the strictTypes encoding configuration is false, and will result generate an encoding error if strictTypes is true.

Clients wishing to ensure that encoding and then decoding results in a Datum equal to the original value should use only Datum types natively supported in JSON (see Supported Types , and ensure that duplicate keys are not present in the source Datum (duplicate keys in a Datum map are typically an error, but the interface does allow them to be created). Enabling the strictTypes option verifies that the types in encoded JSON fields can be decoded back into Datum fields of equal value. So, for example, enabling strictTypes will result in encode producing a positive return status if one of the encoded types is an int, because decoding the resulting JSON will produce a double. The strictTypes option does not, however, verify that a Datum map contains unique keys. For double fields, strictTypes will result in encode returning a positive value if a singular double value is encountered, such as a NaN or Infinity.

The order of key/value pairs in objects in textual JSON passed to decode is preserved in the decoded Datum. If multiple entries with the same key are present in an object, decode will return the first such value.

The order of key/value pairs (DatumMapEntry) in Datum objects passed to encode will be preserved in the resulting JSON, and all keys/value pairs will be present (including duplicate keys). Duplicate keys will be rendered in an encoded JSON, even if strictTypes checking is enabled. Note that a Datum map containing duplicate keys is typically an error (the result of a incorrectly constructed Datum), but the public interface for Datum does not disallow creating such a Datum object.

Supported Types

The table below describes the set of types that a Datum may be, whether it can be encoded to JSON, and, if so, which JSON type will be decoded if the value is read back in.

The encode routines will return a negative (error) status if the input datum contains any field that is not JSON-able in this table.

If the DatumEncoderOptions parameter is passed to an encode routine and its strictTypes field is true, then encode will return a positive value if any value is encoded where the dataType and decode type columns in this table are different (and therefore the strictTypes ok? column is no).

dataType JSON-able JSON type decode type strictTypes ok?
-------- --------- --------- ----------- ---------------
e_NIL yes null e_NIL yes
e_INTEGER yes number e_DOUBLE no
e_DOUBLE yes number e_DOUBLE yes [1]
e_STRING yes string e_STRING yes
e_BOOLEAN yes bool e_BOOLEAN yes
e_ERROR no N/A N/A no
e_DATE yes string e_STRING no
e_TIME yes string e_STRING no
e_DATETIME yes string e_STRING no
e_DATETIME_INTERVAL yes string e_STRING no
e_INTEGER64 yes number e_DOUBLE no
e_USERDEFINED no N/A N/A no
e_BINARY no N/A N/A no
e_DECIMAL64 [2] yes number e_STRING no
dataType JSON-able JSON type decode type strictTypes ok?
-------- --------- --------- ----------- ---------------
e_ARRAY yes array e_ARRAY yes
e_MAP yes map e_MAP yes
e_INT_MAP no N/A N/A no
[1] Singular double values (e.g., inf and nan) are not permitted if
strictTypes is 'true', and will be rendered as strings if 'strictTypes'
is 'false'.
[2] If the 'encodeQuotedDecimal64' attribute in the 'DatumEncoderOptions' is
'true' (the default), the 'Decimal64' values will be encoded as strings,
otherwise they will be encoded as numbers. Encoding a Decimal64 as a
JSON number will frequently result in it being later decoded as a binary
floating point number, and in the process losing digits of precision
that were the point of using the Decimal64 type in the first place.
Care should be taken when setting this option to 'false' (though it may
be useful when communicating with endpoints that are known to correctly
handle high precision JSON numbers).

Usage

This section illustrates intended use of this component.

Example 1: Encode (and decode) Datum to (and from) a JSON string.

The following example illustrates encoding a Datum as a JSON string and then decoding that JSON string back into a Datum object.

First, we create our Datum object, using the bdld::DatumMaker utility:

bdlma::BufferedSequentialAllocator bsa(buffer.buffer(), sizeof(buffer));
bdld::Datum books = m.a(m.m("Author", "Ann Leckie",
"Title", "Ancillary Justice"),
m.m("Author", "John Scalzi",
"Title", "Redshirts"));
Definition bdld_datummaker.h:158
Definition bdld_datum.h:787
Definition bdlma_bufferedsequentialallocator.h:265
Definition bsls_alignedbuffer.h:261
char * buffer()
Definition bsls_alignedbuffer.h:294

Then, we convert the books Datum to formatted JSON:

bookOptions.setSpacesPerLevel(4);
bsl::string booksJSON(&bsa);
int rc = baljsn::DatumUtil::encode(&booksJSON, books, bookOptions);
if (0 != rc) {
// handle error
}
Definition baljsn_datumencoderoptions.h:169
void setEncodingStyle(baljsn::EncodingStyle::Value value)
Definition baljsn_datumencoderoptions.h:317
void setSpacesPerLevel(int value)
Definition baljsn_datumencoderoptions.h:331
Definition bslstl_string.h:1281
static int encode(bsl::string *result, const bdld::Datum &datum)
Definition baljsn_datumutil.h:507
@ e_PRETTY
Definition baljsn_encodingstyle.h:80

Next, we compare the result to the JSON we expect:

const bsl::string EXPECTED_BOOKS_JSON = "[\n"
" {\n"
" \"Author\" : \"Ann Leckie\",\n"
" \"Title\" : \"Ancillary Justice\"\n"
" },\n"
" {\n"
" \"Author\" : \"John Scalzi\",\n"
" \"Title\" : \"Redshirts\"\n"
" }\n"
"]";
assert(EXPECTED_BOOKS_JSON == booksJSON);

Finally, we can decode the booksJSON and make sure we got the same value back:

bdld::ManagedDatum decodedBooks;
rc = baljsn::DatumUtil::decode(&decodedBooks, booksJSON);
if (0 != rc) {
// handle error
}
assert(*decodedBooks == books);
Definition bdld_manageddatum.h:266
static int decode(bdld::ManagedDatum *result, const bsl::string_view &json)
Definition baljsn_datumutil.h:459

Example 2: Converting JSON to Datum

The following example illustrates decoding a string into a Datum object.

First, we create the JSON source, in both plain and formatted forms:

const bsl::string plainFamilyJSON = "["
"{\"firstName\":\"Homer\","
"\"age\":34}"
",{\"firstName\":\"Marge\","
"\"age\":34}"
",{\"firstName\":\"Bart\","
"\"age\":10}"
",{\"firstName\":\"Lisa\","
"\"age\":8}"
",{\"firstName\":\"Maggie\","
"\"age\":1}"
"]";
// Note that whitespace formatting is unimportant as long as the result is
// legal JSON. This will generate the same 'Datum' as the single-line form
// above.
const bsl::string formattedFamilyJSON =
"[\n"
" {\n"
" \"firstName\" : \"Homer\",\n"
" \"age\" : 34\n"
" },\n"
" {\n"
" \"firstName\" : \"Marge\",\n"
" \"age\" : 34\n"
" },\n"
" {\n"
" \"firstName\" : \"Bart\",\n"
" \"age\" : 10\n"
" },\n"
" {\n"
" \"firstName\" : \"Lisa\",\n"
" \"age\" : 8\n"
" },\n"
" {\n"
" \"firstName\" : \"Maggie\",\n"
" \"age\" : 1\n"
" }\n"
"]";

Then, we convert the single-line string to a Datum:

rc = baljsn::DatumUtil::decode(&family, plainFamilyJSON);
if (0 != rc) {
// handle error
}

Next, we convert the formatted string to another Datum and make sure that the results match:

rc = baljsn::DatumUtil::decode(&family2, formattedFamilyJSON);
if (0 != rc) {
// handle error
}
assert(family == family2);

Finally, we make sure that the structure of the resulting datum is as we expect.

assert(family->isArray());
assert(5 == family->theArray().length());
const bdld::Datum &lisa = family->theArray()[3];
assert(lisa.isMap());
assert(2 == lisa.theMap().size());
assert("Lisa" == lisa.theMap().find("firstName")->theString());
assert(8 == lisa.theMap().find("age")->theDouble());
size_type length() const
Return a const pointer to the length of the array.
Definition bdld_datum.h:4886
const Datum * find(const bslstl::StringRef &key) const
size_type size() const BSLS_KEYWORD_NOEXCEPT
Definition bdld_datum.h:5221
DatumArrayRef theArray() const
Definition bdld_datum.h:4257
bool isArray() const
Definition bdld_datum.h:4119
DatumMapRef theMap() const
Definition bdld_datum.h:4458
bslstl::StringRef theString() const
Definition bdld_datum.h:4497
bool isMap() const
Definition bdld_datum.h:4227
double theDouble() const
Definition bdld_datum.h:4378

Notice that the type of "age" is double, since "age" was encoded as a number, and double is the supported representation of a JSON number (see Supported Types .