BDE 4.14.0 Production release
Loading...
Searching...
No Matches
baljsn_datumutil.h
Go to the documentation of this file.
1/// @file baljsn_datumutil.h
2///
3/// The content of this file has been pre-processed for Doxygen.
4///
5
6
7// baljsn_datumutil.h -*-C++-*-
8#ifndef INCLUDED_BALJSN_DATUMUTIL
9#define INCLUDED_BALJSN_DATUMUTIL
10
11#include <bsls_ident.h>
12BSLS_IDENT("$Id$ $CSID$")
13
14/// @defgroup baljsn_datumutil baljsn_datumutil
15/// @brief Provide utilities converting between `bdld::Datum` and JSON data.
16/// @addtogroup bal
17/// @{
18/// @addtogroup baljsn
19/// @{
20/// @addtogroup baljsn_datumutil
21/// @{
22///
23/// <h1> Outline </h1>
24/// * <a href="#baljsn_datumutil-purpose"> Purpose</a>
25/// * <a href="#baljsn_datumutil-classes"> Classes </a>
26/// * <a href="#baljsn_datumutil-description"> Description </a>
27/// * <a href="#baljsn_datumutil-mapping-data-types-between-datum-and-json"> Mapping Data Types between Datum and JSON </a>
28/// * <a href="#baljsn_datumutil-supported-types"> Supported Types </a>
29/// * <a href="#baljsn_datumutil-usage"> Usage </a>
30/// * <a href="#baljsn_datumutil-example-1-encode-a-json-string"> Example 1: Encode (and decode) Datum to (and from) a JSON string. </a>
31/// * <a href="#baljsn_datumutil-example-2-converting-json-to-datum"> Example 2: Converting JSON to Datum </a>
32///
33/// # Purpose {#baljsn_datumutil-purpose}
34/// Provide utilities converting between `bdld::Datum` and JSON data.
35///
36/// # Classes {#baljsn_datumutil-classes}
37///
38/// - baljsn::DatumUtil: utilities converting between `bdld::Datum` and JSON data
39///
40/// # Description {#baljsn_datumutil-description}
41/// This component provides a struct, `baljsn::DatumUtil`, that is
42/// a namespace for a suite of functions that convert a `bdld::Datum` into a
43/// JSON string, and back.
44///
45/// ## Mapping Data Types between Datum and JSON {#baljsn_datumutil-mapping-data-types-between-datum-and-json}
46///
47///
48/// While most scalar types supported by `Datum` can be encoded into a JSON
49/// string, only the subset of types represented natively in JSON -- number
50/// (represented in C++ as a `double`), `string`, `bool`, `null`, array, and map
51/// types -- will be populated in a `Datum` decoded from a JSON string. If one
52/// were to encode a `Datum` containing a type not natively supported by JSON,
53/// if that JSON string were decoded back into a `Datum` object, the resulting
54/// `Datum` would not be equal to the original value. For example, a `Datum`
55/// containing an integer would be encoded into a JSON number, and then decoded
56/// back into a `Datum` using `double` to represent that number. Note that
57/// `DatumUtil` uses a *more permissive* parser for numerical values than the
58/// strict JSON standard specifies. In particular, it is possible to parse
59/// `NaN`, `Inf`, or `Infinity` into the corresponding singular `double` values
60/// even though the JSON standard does not permit this, so applications should
61/// be ready to handle these kinds of values. Also note that the `encode`
62/// routines do not encode these singular `double` values in these parseable
63/// formats. Singular `double` values will be rendered as strings (e.g., "+inf"
64/// or "nan") if the `strictTypes` encoding configuration is `false`, and will
65/// result generate an encoding error if `strictTypes` is `true`.
66///
67/// Clients wishing to ensure that encoding and then decoding results in a
68/// `Datum` equal to the original value should use only `Datum` types natively
69/// supported in JSON (see @ref baljsn_datumutil-supported-types , and ensure that duplicate
70/// keys are not present in the source `Datum` (duplicate keys in a `Datum` map
71/// are typically an error, but the interface does allow them to be created).
72/// Enabling the `strictTypes` option verifies that the types in encoded JSON
73/// fields can be decoded back into `Datum` fields of equal value. So, for
74/// example, enabling `strictTypes` will result in `encode` producing a positive
75/// return status if one of the encoded types is an `int`, because decoding the
76/// resulting JSON will produce a `double`. The `strictTypes` option does not,
77/// however, verify that a Datum map contains unique keys. For `double` fields,
78/// `strictTypes` will result in `encode` returning a positive value if a
79/// singular `double` value is encountered, such as a NaN or Infinity.
80///
81/// The order of key/value pairs in objects in textual JSON passed to `decode`
82/// is preserved in the decoded `Datum`. If multiple entries with the same
83/// `key` are present in an object, `decode` will return the *first* such value.
84///
85/// The order of key/value pairs (`DatumMapEntry`) in `Datum` objects passed to
86/// `encode` will be preserved in the resulting `JSON`, and all keys/value pairs
87/// will be present (including duplicate keys). Duplicate keys will be rendered
88/// in an encoded JSON, even if `strictTypes` checking is enabled. Note that a
89/// Datum map containing duplicate keys is typically an error (the result of a
90/// incorrectly constructed Datum), but the public interface for Datum does not
91/// disallow creating such a `Datum` object.
92///
93/// ## Supported Types {#baljsn_datumutil-supported-types}
94///
95///
96/// The table below describes the set of types that a `Datum` may be, whether it
97/// can be `encode`d to JSON, and, if so, which JSON type will be `decode`d if
98/// the value is read back in.
99///
100/// The `encode` routines will return a negative (error) status if the input
101/// `datum` contains any field that is not `JSON-able` in this table.
102///
103/// If the `DatumEncoderOptions` parameter is passed to an `encode` routine and
104/// its `strictTypes` field is `true`, then `encode` will return a positive
105/// value if any value is encoded where the `dataType` and `decode type` columns
106/// in this table are different (and therefore the `strictTypes ok?` column is
107/// `no`).
108///
109/// @code
110/// dataType JSON-able JSON type decode type strictTypes ok?
111/// -------- --------- --------- ----------- ---------------
112/// e_NIL yes null e_NIL yes
113/// e_INTEGER yes number e_DOUBLE no
114/// e_DOUBLE yes number e_DOUBLE yes [1]
115/// e_STRING yes string e_STRING yes
116/// e_BOOLEAN yes bool e_BOOLEAN yes
117/// e_ERROR no N/A N/A no
118/// e_DATE yes string e_STRING no
119/// e_TIME yes string e_STRING no
120/// e_DATETIME yes string e_STRING no
121/// e_DATETIME_INTERVAL yes string e_STRING no
122/// e_INTEGER64 yes number e_DOUBLE no
123/// e_USERDEFINED no N/A N/A no
124/// e_BINARY no N/A N/A no
125/// e_DECIMAL64 [2] yes number e_STRING no
126///
127/// dataType JSON-able JSON type decode type strictTypes ok?
128/// -------- --------- --------- ----------- ---------------
129/// e_ARRAY yes array e_ARRAY yes
130/// e_MAP yes map e_MAP yes
131/// e_INT_MAP no N/A N/A no
132///
133/// [1] Singular double values (e.g., inf and nan) are not permitted if
134/// strictTypes is 'true', and will be rendered as strings if 'strictTypes'
135/// is 'false'.
136/// [2] If the 'encodeQuotedDecimal64' attribute in the 'DatumEncoderOptions' is
137/// 'true' (the default), the 'Decimal64' values will be encoded as strings,
138/// otherwise they will be encoded as numbers. Encoding a Decimal64 as a
139/// JSON number will frequently result in it being later decoded as a binary
140/// floating point number, and in the process losing digits of precision
141/// that were the point of using the Decimal64 type in the first place.
142/// Care should be taken when setting this option to 'false' (though it may
143/// be useful when communicating with endpoints that are known to correctly
144/// handle high precision JSON numbers).
145/// @endcode
146/// * *dataType* - the `Datum` type value returned by the `type()`
147/// * *JSON-able* - whether the type can be `encode`d by this component.
148/// * *JSON type* - the JSON type used to `encode` this value, if supported.
149/// * *decode type* - the `Datum` type this `encode`d value would be
150/// `decode`d into.
151/// * *strictTypes ok?* - `encode` will return 0 on success even if
152/// `options->strictTypes()` is `true`.
153///
154/// ## Usage {#baljsn_datumutil-usage}
155///
156///
157/// This section illustrates intended use of this component.
158///
159/// ### Example 1: Encode (and decode) Datum to (and from) a JSON string. {#baljsn_datumutil-example-1-encode-a-json-string}
160///
161///
162/// The following example illustrates encoding a `Datum` as a JSON string and
163/// then decoding that JSON string back into a `Datum` object.
164///
165/// First, we create our `Datum` object, using the `bdld::DatumMaker` utility:
166/// @code
167/// bsls::AlignedBuffer<8 * 1024> buffer;
168/// bdlma::BufferedSequentialAllocator bsa(buffer.buffer(), sizeof(buffer));
169/// bdld::DatumMaker m(&bsa);
170///
171/// bdld::Datum books = m.a(m.m("Author", "Ann Leckie",
172/// "Title", "Ancillary Justice"),
173/// m.m("Author", "John Scalzi",
174/// "Title", "Redshirts"));
175/// @endcode
176/// Then, we convert the `books` `Datum` to formatted JSON:
177/// @code
178/// baljsn::DatumEncoderOptions bookOptions;
179/// bookOptions.setEncodingStyle(baljsn::EncodingStyle::e_PRETTY);
180/// bookOptions.setSpacesPerLevel(4);
181/// bsl::string booksJSON(&bsa);
182///
183/// int rc = baljsn::DatumUtil::encode(&booksJSON, books, bookOptions);
184/// if (0 != rc) {
185/// // handle error
186/// }
187/// @endcode
188/// Next, we compare the result to the JSON we expect:
189/// @code
190/// const bsl::string EXPECTED_BOOKS_JSON = "[\n"
191/// " {\n"
192/// " \"Author\" : \"Ann Leckie\",\n"
193/// " \"Title\" : \"Ancillary Justice\"\n"
194/// " },\n"
195/// " {\n"
196/// " \"Author\" : \"John Scalzi\",\n"
197/// " \"Title\" : \"Redshirts\"\n"
198/// " }\n"
199/// "]";
200///
201/// assert(EXPECTED_BOOKS_JSON == booksJSON);
202/// @endcode
203/// Finally, we can decode the `booksJSON` and make sure we got the same value
204/// back:
205/// @code
206/// bdld::ManagedDatum decodedBooks;
207/// rc = baljsn::DatumUtil::decode(&decodedBooks, booksJSON);
208/// if (0 != rc) {
209/// // handle error
210/// }
211/// assert(*decodedBooks == books);
212/// @endcode
213/// ### Example 2: Converting JSON to Datum {#baljsn_datumutil-example-2-converting-json-to-datum}
214///
215///
216/// The following example illustrates decoding a string into a `Datum` object.
217///
218/// First, we create the JSON source, in both plain and formatted forms:
219/// @code
220/// const bsl::string plainFamilyJSON = "["
221/// "{\"firstName\":\"Homer\","
222/// "\"age\":34}"
223/// ",{\"firstName\":\"Marge\","
224/// "\"age\":34}"
225/// ",{\"firstName\":\"Bart\","
226/// "\"age\":10}"
227/// ",{\"firstName\":\"Lisa\","
228/// "\"age\":8}"
229/// ",{\"firstName\":\"Maggie\","
230/// "\"age\":1}"
231/// "]";
232///
233/// // Note that whitespace formatting is unimportant as long as the result is
234/// // legal JSON. This will generate the same 'Datum' as the single-line form
235/// // above.
236/// const bsl::string formattedFamilyJSON =
237/// "[\n"
238/// " {\n"
239/// " \"firstName\" : \"Homer\",\n"
240/// " \"age\" : 34\n"
241/// " },\n"
242/// " {\n"
243/// " \"firstName\" : \"Marge\",\n"
244/// " \"age\" : 34\n"
245/// " },\n"
246/// " {\n"
247/// " \"firstName\" : \"Bart\",\n"
248/// " \"age\" : 10\n"
249/// " },\n"
250/// " {\n"
251/// " \"firstName\" : \"Lisa\",\n"
252/// " \"age\" : 8\n"
253/// " },\n"
254/// " {\n"
255/// " \"firstName\" : \"Maggie\",\n"
256/// " \"age\" : 1\n"
257/// " }\n"
258/// "]";
259/// @endcode
260/// Then, we convert the single-line `string` to a `Datum`:
261/// @code
262/// bdld::ManagedDatum family;
263/// rc = baljsn::DatumUtil::decode(&family, plainFamilyJSON);
264/// if (0 != rc) {
265/// // handle error
266/// }
267/// @endcode
268/// Next, we convert the formatted `string` to another `Datum` and make sure
269/// that the results match:
270/// @code
271/// bdld::ManagedDatum family2;
272/// rc = baljsn::DatumUtil::decode(&family2, formattedFamilyJSON);
273/// if (0 != rc) {
274/// // handle error
275/// }
276/// assert(family == family2);
277/// @endcode
278/// Finally, we make sure that the structure of the resulting datum is as we
279/// expect.
280/// @code
281/// assert(family->isArray());
282/// assert(5 == family->theArray().length());
283///
284/// const bdld::Datum &lisa = family->theArray()[3];
285///
286/// assert(lisa.isMap());
287/// assert(2 == lisa.theMap().size());
288/// assert("Lisa" == lisa.theMap().find("firstName")->theString());
289/// assert(8 == lisa.theMap().find("age")->theDouble());
290/// @endcode
291/// Notice that the `type` of "age" is `double`, since "age" was encoded as a
292/// number, and `double` is the supported representation of a JSON number (see
293/// @ref baljsn_datumutil-supported-types .
294/// @}
295/** @} */
296/** @} */
297
298/** @addtogroup bal
299 * @{
300 */
301/** @addtogroup baljsn
302 * @{
303 */
304/** @addtogroup baljsn_datumutil
305 * @{
306 */
307
308#include <balscm_version.h>
309
312
313#include <bdld_datum.h>
314#include <bdld_manageddatum.h>
316
317#include <bsls_libraryfeatures.h>
318
319#include <bsl_iosfwd.h>
320#include <bsl_streambuf.h>
321#include <bsl_string.h>
322#include <bsl_string_view.h>
323
324#include <string>
325
326
327namespace baljsn {
328
329class SimpleFormatter;
330
331 // ================
332 // struct DatumUtil
333 // ================
334
335/// This `struct` provides a namespace for a suite of functions that convert
336/// between a JSON formated string and a `bdld::Datum`.
337struct DatumUtil {
338
339 // CLASS METHODS
340
341 static int decode(bdld::ManagedDatum *result,
342 const bsl::string_view& json);
343 static int decode(bdld::ManagedDatum *result,
344 const bsl::string_view& json,
345 const DatumDecoderOptions& options);
346 static int decode(bdld::ManagedDatum *result,
347 bsl::ostream *errorStream,
348 const bsl::string_view& json);
349 /// Decode the specified `json` into the specified `result`. If the
350 /// optionally specified `errorStream` is non-null, a description of any
351 /// errors that occur during parsing will be output to this stream. If
352 /// the optionally specified `options` argument is not present, treat it
353 /// as a default-constructed `DatumDecoderOptions`. Return 0 on
354 /// success, and a negative value if `json` could not be decoded (either
355 /// because it is ill-formed, or if a constraint imposed by `option` is
356 /// violated). An error status will be returned if `json` contains
357 /// arrays or objects that are nested beyond a depth configured by
358 /// `options.maxNestedDepth()`. The mapping of types in JSON to the
359 /// types supported by `Datum` is described in @ref baljsn_datumutil-supported-types .
360 static int decode(bdld::ManagedDatum *result,
361 bsl::ostream *errorStream,
362 const bsl::string_view& json,
363 const DatumDecoderOptions& options);
364
365 static int decode(bdld::ManagedDatum *result,
366 bsl::streambuf *jsonBuffer);
367 static int decode(bdld::ManagedDatum *result,
368 bsl::streambuf *jsonBuffer,
369 const DatumDecoderOptions& options);
370 static int decode(bdld::ManagedDatum *result,
371 bsl::ostream *errorStream,
372 bsl::streambuf *jsonBuffer);
373 /// Decode the JSON string provided by the specified `jsonBuffer` into
374 /// the specified `result`. If the optionally specified `errorStream`
375 /// is non-null, a description of any errors that occur during parsing
376 /// will be output to this stream. If the optionally specified
377 /// `options` argument is not present, treat it as a default-constructed
378 /// `DatumDecoderOptions`. Return 0 on success, and a negative value if
379 /// `json` could not be decoded (either because it is ill-formed, or if
380 /// a constraint imposed by `option` is violated). An error status will
381 /// be returned if `json` contains arrays or objects that are nested
382 /// beyond a depth configured by `options.maxNestedDepth()`. The
383 /// mapping of types in JSON to the types supported by `Datum` is
384 /// described in @ref baljsn_datumutil-supported-types .
385 static int decode(bdld::ManagedDatum *result,
386 bsl::ostream *errorStream,
387 bsl::streambuf *jsonBuffer,
388 const DatumDecoderOptions& options);
389
390 static int encode(bsl::string *result,
391 const bdld::Datum& datum);
392 /// Encode the specified `datum` as a JSON string, and load the
393 /// specified `result` with the encoded JSON string. Return 0 on
394 /// success, and a negative value if `datum` could not be encoded (with
395 /// no effect on `result`). If the optionally specified `options`
396 /// argument is not present, treat it as a default-constructed
397 /// `DatumEncoderOptions`. If `options.strictTypes` is `true` and a
398 /// type that is not supported by JSON, or a singular double value
399 /// (e.g., NaN or infinity) is being encoded (see @ref baljsn_datumutil-supported-types
400 /// return a positive value, but also populate `result` with an encoded
401 /// JSON string (i.e., the value of `result` is the same regardless of
402 /// the `strictTypes` option, but if `strictTypes` is `true` a non-zero
403 /// positive status will be returned). The mapping of types supported
404 /// by `Datum` to JSON types is described in @ref baljsn_datumutil-supported-types .
405 static int encode(std::string *result,
406 const bdld::Datum& datum);
407#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR_STRING
408 static int encode(std::pmr::string *result,
409 const bdld::Datum& datum);
410#endif
411 static int encode(bsl::string *result,
412 const bdld::Datum& datum,
413 const DatumEncoderOptions& options);
414 static int encode(std::string *result,
415 const bdld::Datum& datum,
416 const DatumEncoderOptions& options);
417#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR_STRING
418 static int encode(std::pmr::string *result,
419 const bdld::Datum& datum,
420 const DatumEncoderOptions& options);
421#endif
422
423 static int encode(bsl::ostream& stream,
424 const bdld::Datum& datum);
425 /// Encode the specified `datum` as a JSON string, and write it into the
426 /// specified `stream`. Return 0 on success, and a negative value if
427 /// `datum` could not be encoded (which may leave a partial JSON
428 /// sequence on the `stream`). If the optionally specified `options`
429 /// argument is not present, treat it as a default-constructed
430 /// `DatumEncoderOptions`. If `options.strictTypes` is `true` and a
431 /// type that is not supported by JSON, or a singular double value
432 /// (e.g., NaN or infinity) is being encoded (see @ref baljsn_datumutil-supported-types
433 /// return a positive value, but also populate `result` with an encoded
434 /// JSON string (i.e., the value of `result` is the same regardless of
435 /// the `strictTypes` option, but if `strictTypes` is `true` a non-zero
436 /// positive status will be returned). The mapping of types supported
437 /// by `Datum` to JSON types is described in @ref baljsn_datumutil-supported-types .
438 static int encode(bsl::ostream& stream,
439 const bdld::Datum& datum,
440 const DatumEncoderOptions& options);
441};
442
443// ============================================================================
444// INLINE DEFINITIONS
445// ============================================================================
446
447// CLASS METHODS
448
449inline
451 const bsl::string_view& json,
452 const DatumDecoderOptions& options)
453{
454 bdlsb::FixedMemInStreamBuf buffer(json.data(), json.length());
455 return decode(result, 0, &buffer, options);
456}
457
458inline
460 const bsl::string_view& json)
461{
462 return decode(result, json, DatumDecoderOptions());
463}
464
465inline
467 bsl::ostream *errorStream,
468 const bsl::string_view& json,
469 const DatumDecoderOptions& options)
470{
471 bdlsb::FixedMemInStreamBuf buffer(json.data(), json.length());
472 return decode(result, errorStream, &buffer, options);
473}
474
475inline
477 bsl::ostream *errorStream,
478 const bsl::string_view& json)
479{
480 return decode(result, errorStream, json, DatumDecoderOptions());
481}
482
483inline
485 bsl::streambuf *jsonBuffer,
486 const DatumDecoderOptions& options)
487{
488 return decode(result, 0, jsonBuffer, options);
489}
490
491inline
493 bsl::streambuf *jsonBuffer)
494{
495 return decode(result, 0, jsonBuffer, DatumDecoderOptions());
496}
497
498inline
500 bsl::ostream *errorStream,
501 bsl::streambuf *jsonBuffer)
502{
503 return decode(result, errorStream, jsonBuffer, DatumDecoderOptions());
504}
505
506inline
507int DatumUtil::encode(bsl::string *result, const bdld::Datum& datum)
508{
509 return encode(result, datum, DatumEncoderOptions());
510}
511
512inline
513int DatumUtil::encode(std::string *result, const bdld::Datum& datum)
514{
515 return encode(result, datum, DatumEncoderOptions());
516}
517
518#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR_STRING
519inline
520int DatumUtil::encode(std::pmr::string *result,
521 const bdld::Datum& datum)
522{
523 return encode(result, datum, DatumEncoderOptions());
524}
525#endif
526
527inline
528int DatumUtil::encode(bsl::ostream& stream, const bdld::Datum& datum)
529{
530 return encode(stream, datum, DatumEncoderOptions());
531}
532
533
534} // close package namespace
535
536
537#endif
538
539// ----------------------------------------------------------------------------
540// Copyright 2020 Bloomberg Finance L.P.
541//
542// Licensed under the Apache License, Version 2.0 (the "License");
543// you may not use this file except in compliance with the License.
544// You may obtain a copy of the License at
545//
546// http://www.apache.org/licenses/LICENSE-2.0
547//
548// Unless required by applicable law or agreed to in writing, software
549// distributed under the License is distributed on an "AS IS" BASIS,
550// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
551// See the License for the specific language governing permissions and
552// limitations under the License.
553// ----------------------------- END-OF-FILE ----------------------------------
554
555/** @} */
556/** @} */
557/** @} */
Definition baljsn_datumdecoderoptions.h:126
Definition baljsn_datumencoderoptions.h:169
Definition bdld_datum.h:787
Definition bdld_manageddatum.h:266
Definition bdlsb_fixedmeminstreambuf.h:187
Definition bslstl_stringview.h:441
BSLS_KEYWORD_CONSTEXPR size_type length() const BSLS_KEYWORD_NOEXCEPT
Return the length of this view.
Definition bslstl_stringview.h:1685
BSLS_KEYWORD_CONSTEXPR const_pointer data() const BSLS_KEYWORD_NOEXCEPT
Definition bslstl_stringview.h:1760
Definition bslstl_string.h:1281
#define BSLS_IDENT(str)
Definition bsls_ident.h:195
Definition baljsn_datumdecoderoptions.h:113
Definition baljsn_datumutil.h:337
static int encode(bsl::string *result, const bdld::Datum &datum, const DatumEncoderOptions &options)
static int encode(bsl::ostream &stream, const bdld::Datum &datum, const DatumEncoderOptions &options)
static int encode(bsl::string *result, const bdld::Datum &datum)
Definition baljsn_datumutil.h:507
static int decode(bdld::ManagedDatum *result, const bsl::string_view &json)
Definition baljsn_datumutil.h:459
static int encode(std::string *result, const bdld::Datum &datum, const DatumEncoderOptions &options)
static int decode(bdld::ManagedDatum *result, bsl::ostream *errorStream, bsl::streambuf *jsonBuffer, const DatumDecoderOptions &options)