BDE 4.39.x 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::DatumArrayRef &familyArray = family->theArray();
285/// assert(5 == familyArray.length());
286///
287/// const bdld::Datum &lisa = familyArray[3];
288///
289/// assert(lisa.isMap());
290/// assert(2 == lisa.theMap().size());
291/// assert("Lisa" == lisa.theMap().find("firstName")->theString());
292/// assert(8 == lisa.theMap().find("age")->theDouble());
293/// @endcode
294/// Notice that the `type` of "age" is `double`, since "age" was encoded as a
295/// number, and `double` is the supported representation of a JSON number (see
296/// @ref baljsn_datumutil-supported-types ).
297/// @}
298/** @} */
299/** @} */
300
301/** @addtogroup bal
302 * @{
303 */
304/** @addtogroup baljsn
305 * @{
306 */
307/** @addtogroup baljsn_datumutil
308 * @{
309 */
310
311#include <balscm_version.h>
312
315
316#include <bdld_datum.h>
317#include <bdld_manageddatum.h>
319
320#include <bsls_libraryfeatures.h>
321
322#include <bsl_iosfwd.h>
323#include <bsl_streambuf.h>
324#include <bsl_string.h>
325#include <bsl_string_view.h>
326
327#include <string>
328
329
330namespace baljsn {
331
332class SimpleFormatter;
333
334 // ================
335 // struct DatumUtil
336 // ================
337
338/// This `struct` provides a namespace for a suite of functions that convert
339/// between a JSON formated string and a `bdld::Datum`.
340///
341/// See @ref baljsn_datumutil
342struct DatumUtil {
343
344 // CLASS METHODS
345
346 /// Decode the specified `json` into the specified `result`. If the
347 /// optionally specified `errorStream` is non-null, a description of any
348 /// errors that occur during parsing will be output to this stream. If
349 /// the optionally specified `options` argument is not present, treat it
350 /// as a default-constructed `DatumDecoderOptions`. Return 0 on
351 /// success, and a negative value if `json` could not be decoded (either
352 /// because it is ill-formed, or if a constraint imposed by `option` is
353 /// violated). An error status will be returned if `json` contains
354 /// arrays or objects that are nested beyond a depth configured by
355 /// `options.maxNestedDepth()`. The mapping of types in JSON to the
356 /// types supported by `Datum` is described in @ref baljsn_datumutil-supported-types .
357 static int decode(bdld::ManagedDatum *result,
358 const bsl::string_view& json);
359 static int decode(bdld::ManagedDatum *result,
360 const bsl::string_view& json,
361 const DatumDecoderOptions& options);
362 static int decode(bdld::ManagedDatum *result,
363 bsl::ostream *errorStream,
364 const bsl::string_view& json);
365 static int decode(bdld::ManagedDatum *result,
366 bsl::ostream *errorStream,
367 const bsl::string_view& json,
368 const DatumDecoderOptions& options);
369
370 /// Decode the JSON string provided by the specified `jsonBuffer` into
371 /// the specified `result`. If the optionally specified `errorStream`
372 /// is non-null, a description of any errors that occur during parsing
373 /// will be output to this stream. If the optionally specified
374 /// `options` argument is not present, treat it as a default-constructed
375 /// `DatumDecoderOptions`. Return 0 on success, and a negative value if
376 /// `json` could not be decoded (either because it is ill-formed, or if
377 /// a constraint imposed by `option` is violated). An error status will
378 /// be returned if `json` contains arrays or objects that are nested
379 /// beyond a depth configured by `options.maxNestedDepth()`. The
380 /// mapping of types in JSON to the types supported by `Datum` is
381 /// described in @ref baljsn_datumutil-supported-types .
382 static int decode(bdld::ManagedDatum *result,
383 bsl::streambuf *jsonBuffer);
384 static int decode(bdld::ManagedDatum *result,
385 bsl::streambuf *jsonBuffer,
386 const DatumDecoderOptions& options);
387 static int decode(bdld::ManagedDatum *result,
388 bsl::ostream *errorStream,
389 bsl::streambuf *jsonBuffer);
390 static int decode(bdld::ManagedDatum *result,
391 bsl::ostream *errorStream,
392 bsl::streambuf *jsonBuffer,
393 const DatumDecoderOptions& options);
394
395 /// Encode the specified `datum` as a JSON string, and load the
396 /// specified `result` with the encoded JSON string. Return 0 on
397 /// success, and a negative value if `datum` could not be encoded (with
398 /// no effect on `result`). If the optionally specified `options`
399 /// argument is not present, treat it as a default-constructed
400 /// `DatumEncoderOptions`. If `options.strictTypes` is `true` and a
401 /// type that is not supported by JSON, or a singular double value
402 /// (e.g., NaN or infinity) is being encoded (see @ref baljsn_datumutil-supported-types )
403 /// return a positive value, but also populate `result` with an encoded
404 /// JSON string (i.e., the value of `result` is the same regardless of
405 /// the `strictTypes` option, but if `strictTypes` is `true` a non-zero
406 /// positive status will be returned). The mapping of types supported
407 /// by `Datum` to JSON types is described in @ref baljsn_datumutil-supported-types .
408 static int encode(bsl::string *result,
409 const bdld::Datum& datum);
410 static int encode(std::string *result,
411 const bdld::Datum& datum);
412#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR_STRING
413 static int encode(std::pmr::string *result,
414 const bdld::Datum& datum);
415#endif
416 static int encode(bsl::string *result,
417 const bdld::Datum& datum,
418 const DatumEncoderOptions& options);
419 static int encode(std::string *result,
420 const bdld::Datum& datum,
421 const DatumEncoderOptions& options);
422#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR_STRING
423 static int encode(std::pmr::string *result,
424 const bdld::Datum& datum,
425 const DatumEncoderOptions& options);
426#endif
427
428 /// Encode the specified `datum` as a JSON string, and write it into the
429 /// specified `stream`. Return 0 on success, and a negative value if
430 /// `datum` could not be encoded (which may leave a partial JSON
431 /// sequence on the `stream`). If the optionally specified `options`
432 /// argument is not present, treat it as a default-constructed
433 /// `DatumEncoderOptions`. If `options.strictTypes` is `true` and a
434 /// type that is not supported by JSON, or a singular double value
435 /// (e.g., NaN or infinity) is being encoded (see @ref baljsn_datumutil-supported-types )
436 /// return a positive value, but also populate `result` with an encoded
437 /// JSON string (i.e., the value of `result` is the same regardless of
438 /// the `strictTypes` option, but if `strictTypes` is `true` a non-zero
439 /// positive status will be returned). The mapping of types supported
440 /// by `Datum` to JSON types is described in @ref baljsn_datumutil-supported-types .
441 static int encode(bsl::ostream& stream,
442 const bdld::Datum& datum);
443 static int encode(bsl::ostream& stream,
444 const bdld::Datum& datum,
445 const DatumEncoderOptions& options);
446};
447
448// ============================================================================
449// INLINE DEFINITIONS
450// ============================================================================
451
452// CLASS METHODS
453
454inline
456 const bsl::string_view& json,
457 const DatumDecoderOptions& options)
458{
459 bdlsb::FixedMemInStreamBuf buffer(json.data(), json.length());
460 return decode(result, 0, &buffer, options);
461}
462
463inline
465 const bsl::string_view& json)
466{
467 return decode(result, json, DatumDecoderOptions());
468}
469
470inline
472 bsl::ostream *errorStream,
473 const bsl::string_view& json,
474 const DatumDecoderOptions& options)
475{
476 bdlsb::FixedMemInStreamBuf buffer(json.data(), json.length());
477 return decode(result, errorStream, &buffer, options);
478}
479
480inline
482 bsl::ostream *errorStream,
483 const bsl::string_view& json)
484{
485 return decode(result, errorStream, json, DatumDecoderOptions());
486}
487
488inline
490 bsl::streambuf *jsonBuffer,
491 const DatumDecoderOptions& options)
492{
493 return decode(result, 0, jsonBuffer, options);
494}
495
496inline
498 bsl::streambuf *jsonBuffer)
499{
500 return decode(result, 0, jsonBuffer, DatumDecoderOptions());
501}
502
503inline
505 bsl::ostream *errorStream,
506 bsl::streambuf *jsonBuffer)
507{
508 return decode(result, errorStream, jsonBuffer, DatumDecoderOptions());
509}
510
511inline
512int DatumUtil::encode(bsl::string *result, const bdld::Datum& datum)
513{
514 return encode(result, datum, DatumEncoderOptions());
515}
516
517inline
518int DatumUtil::encode(std::string *result, const bdld::Datum& datum)
519{
520 return encode(result, datum, DatumEncoderOptions());
521}
522
523#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR_STRING
524inline
525int DatumUtil::encode(std::pmr::string *result,
526 const bdld::Datum& datum)
527{
528 return encode(result, datum, DatumEncoderOptions());
529}
530#endif
531
532inline
533int DatumUtil::encode(bsl::ostream& stream, const bdld::Datum& datum)
534{
535 return encode(stream, datum, DatumEncoderOptions());
536}
537
538
539} // close package namespace
540
541
542#endif
543
544// ----------------------------------------------------------------------------
545// Copyright 2020 Bloomberg Finance L.P.
546//
547// Licensed under the Apache License, Version 2.0 (the "License");
548// you may not use this file except in compliance with the License.
549// You may obtain a copy of the License at
550//
551// http://www.apache.org/licenses/LICENSE-2.0
552//
553// Unless required by applicable law or agreed to in writing, software
554// distributed under the License is distributed on an "AS IS" BASIS,
555// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
556// See the License for the specific language governing permissions and
557// limitations under the License.
558// ----------------------------- END-OF-FILE ----------------------------------
559
560/** @} */
561/** @} */
562/** @} */
Definition baljsn_datumdecoderoptions.h:127
Definition baljsn_datumencoderoptions.h:170
Definition bdld_datum.h:799
Definition bdld_manageddatum.h:271
Definition bdlsb_fixedmeminstreambuf.h:187
Definition bslstl_stringview.h:471
BSLS_KEYWORD_CONSTEXPR size_type length() const BSLS_KEYWORD_NOEXCEPT
Return the length of this view.
Definition bslstl_stringview.h:1913
BSLS_KEYWORD_CONSTEXPR const_pointer data() const BSLS_KEYWORD_NOEXCEPT
Definition bslstl_stringview.h:1988
Definition bslstl_string.h:1252
#define BSLS_IDENT(str)
BSLS_IDENT() - insert string into .comment binary segment (if supported)
Definition bsls_ident.h:238
Definition baljsn_convertfromjsonoptions.h:112
Definition baljsn_datumutil.h:342
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:512
static int decode(bdld::ManagedDatum *result, const bsl::string_view &json)
Definition baljsn_datumutil.h:464
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)