Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component baljsn_datumutil
[Package baljsn]

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

Namespaces

namespace  baljsn

Detailed Description

Outline
Purpose:
Provide utilities converting between bdld::Datum and JSON data.
Classes:
baljsn::DatumUtil utilities converting between bdld::Datum and JSON data
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 below), 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).
  • dataType - the Datum type value returned by the type()
  • JSON-able - whether the type can be encoded by this component.
  • JSON type - the JSON type used to encode this value, if supported.
  • decode type - the Datum type this encoded value would be decoded into.
  • strictTypes ok? - encode will return 0 on success even if options->strictTypes() is true.
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:
  bsls::AlignedBuffer<8 * 1024>      buffer;
  bdlma::BufferedSequentialAllocator bsa(buffer.buffer(), sizeof(buffer));
  bdld::DatumMaker                   m(&bsa);

  bdld::Datum books = m.a(m.m("Author", "Ann Leckie",
                              "Title", "Ancilliary Justice"),
                          m.m("Author", "John Scalzi",
                              "Title", "Redshirts"));
Then, we convert the books Datum to formatted JSON:
  baljsn::DatumEncoderOptions bookOptions;
  bookOptions.setEncodingStyle(baljsn::EncodingStyle::e_PRETTY);
  bookOptions.setSpacesPerLevel(4);
  bsl::string booksJSON(&bsa);

  int rc = baljsn::DatumUtil::encode(&booksJSON, books, bookOptions);
  if (0 != rc) {
      // handle error
  }
Next, we compare the result to the JSON we expect:
  const bsl::string EXPECTED_BOOKS_JSON = R"JSON([
    {
        "Author" : "Ann Leckie",
        "Title" : "Ancilliary Justice"
    },
    {
        "Author" : "John Scalzi",
        "Title" : "Redshirts"
    }
  ])JSON";

  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);
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 = R"JSON([
      {
          "firstName" : "Homer",
          "age" : 34
      },
      {
          "firstName" : "Marge",
          "age" : 34
      },
      {
          "firstName" : "Bart",
          "age" : 10
      },
      {
          "firstName" : "Lisa",
          "age" : 8
      },
      {
          "firstName" : "Maggie",
          "age" : 1
      }
  ])JSON";
Then, we convert the single-line string to a Datum:
  bdld::ManagedDatum family;
  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:
  bdld::ManagedDatum family2;
  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());
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).