// bdljsn_jsonutil.h -*-C++-*- #ifndef INCLUDED_BDLJSN_JSONUTIL #define INCLUDED_BDLJSN_JSONUTIL #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide common non-primitive operations on 'Json' objects. // //@CLASSES: // bdljsn::JsonUtil: namespace for non-primitive operations on 'Json' objects // //@DESCRIPTION: This component provides a namespace 'bdljsn::JsonUtil' // containing utility functions that operate on 'Json' objects. // // The following methods are provided by 'JsonUtil': //: o 'read' populate a 'Json' object from a JSON text document. //: o 'write' populate a JSON text document from a 'Json' object. // ///Configuring the Output Format ///----------------------------- // There are a number of options to configure the output format produced by // 'write': //: o 'sortMembers': sort the members of any Object elements in the output JSON //: (default: 'false') //: o 'style': the style of the resulting output //: o 'e_COMPACT' (default): render with no added white space //: o 'e_ONELINE': render a human readable single-line format (e.g., for //: logging) //: o 'e_PRETTY': render a multi-line human readable format //: o 'spacesPerLevel': for the 'e_PRETTY' style, the number of spaces added //: for each additional nesting level (default 4) //: o 'initialIndentationLevel': for the 'e_PRETTY' style, the number of sets //: of 'spacesPerLevel' spaces added to every line of the output, including //: the first and last lines (default 0) // // The example below shows the various write styles: //.. // Compact: // {"a":1,"b":[]} // // One-line: // {"a": 1, "b": []} // // Pretty: // { // "a": 1, // "b": [] // } //.. // For more information, see the 'bdljsn_writeoptions' and // 'bdljsn_writestyle' components. // ///Handling of Duplicate Keys ///-------------------------- // 'bdljsn::JsonObject' represents a JSON Object having unique keys. If an // Object with duplicate keys is found in a JSON document, 'read' will preserve // the value associated with the FIRST instance of that key. // // Per the JSON RFC (https://www.rfc-editor.org/rfc/rfc8259#section-4): //.. // "The names within an object SHOULD be unique." //.. // That is, the expectation is that a JSON document should have unique keys, // and JSON documents with duplicate keys are not an interoperable // representation. JSON parsing implementations vary on how duplicate keys are // handled (though many represent the object in-memory with unique keys). Note // that preserving the value of the first key is consistent with the behavior // of the existing 'baljsn::DatumUtil' component. // ///Allowing Trailing Text ///---------------------- // By default, 'bdljsn::JsonUtil::read' will report an error for input where a // valid JSON document is followed by additional text unless the trailing text // consists solely of white space characters. This behavior is configured by // the 'bdljsn::ReadOptions' attribute, "allowTrailingText" (which defaults to // 'false'). // // If "allowTrailingText" is 'true', then 'bdljsn::JsonUtil::read' will return // success where a valid JSON document is followed by additional text as long // as that text is separated from the valid JSON by a delimiter character // (i.e., either the JSON text ends in a delimiter, or the text that follows // starts with a delimiter). Here, delimiters are white-space characters, // '[',']','{','}',',', or '"'. Per RFC 8259, white space characters are // Space (0x20), Horizontal tab (0x09), New Line (0x0A), and Carriage Return // (0x0D). // // The table below shows some examples: //.. // * "ATT" = "allowTrailingText" // * Document is only valid where the result is SUCCESS // // +-----------+------------------------+-------------+-----------+ // | Input | ATT = false (default) | ATT = true | Document | // +===========+========================+=============+===========+ // | '[]' | SUCCESS | SUCCESS | [] | // | '[] ' | SUCCESS | SUCCESS | [] | // | '[],' | ERROR | SUCCESS | [] | // | '[]a' | ERROR | SUCCESS | [] | // | 'false ' | SUCCESS | SUCCESS | false | // | 'false,' | ERROR | SUCCESS | false | // | 'falsea' | ERROR | ERROR | | // | '"a"x' | ERROR | SUCCESS | "a" | // +-----------+------------------------+-------------+-----------+ //.. // ///Usage ///----- // This section illustrates the intended use of this component. // ///Example 1: Reading and Writing JSON Data /// - - - - - - - - - - - - - - - - - - - - // This component provides methods for reading and writing JSON data to/from // 'Json' objects. // // First, we define the JSON data we plan to read: //.. // const char *INPUT_JSON = R"JSON({ // "a boolean": true, // "a date": "1970-01-01", // "a number": 2.1, // "an integer": 10, // "array of values": [ // -1, // 0, // 2.718281828459045, // 3.1415926535979, // "abc", // true // ], // "event": { // "date": "1969-07-16", // "description": "Apollo 11 Moon Landing", // "passengers": [ // "Neil Armstrong", // "Buzz Aldrin" // ], // "success": true // } // }})JSON"; //.. // Next, we read the JSON data into a 'Json' object: //.. // bdljsn::Json result; // bdljsn::Error error; // // int rc = bdljsn::JsonUtil::read(&result, &error, INPUT_JSON); // // assert(0 == rc); // // if (0 != rc) { // bsl::cout << "Error message: \"" << error.message() << "\"" // << bsl::endl; // } //.. // Then, we check the values of a few selected fields: //.. // assert(result.type() == JsonType::e_OBJECT); // assert(result["array of values"][2].theNumber().asDouble() // == 2.718281828459045); // assert(result["event"]["date"].theString() == "1969-07-16"); // assert(result["event"]["passengers"][1].theString() == "Buzz Aldrin"); //.. // Finally, we'll 'write' the 'result' back into another string and make sure // we got the same value back, by using the correct 'WriteOptions' to match // the input format: //.. // bsl::string resultString; // // // Set the WriteOptions to match the initial style: // WriteOptions writeOptions; // writeOptions.setStyle(bdljsn::WriteStyle::e_PRETTY); // writeOptions.setInitialIndentLevel(0); // writeOptions.setSpacesPerLevel(2); // writeOptions.setSortMembers(true); // // bdljsn::JsonUtil::write(&resultString, result, writeOptions); // // assert(resultString == INPUT_JSON); //.. // ///Example 2: The Effect of 'options' on 'write' ///- - - - - - - - - - - - - - - - - - - - - - - // By populating a 'WriteOptions' object and passing it to 'write', the format // of the resulting JSON can be controlled. // // First, let's populate a 'Json' object named 'json' from an input string // using 'read', and create an empty 'options' (see 'bdljsn::WriteOptions'): //.. // const bsl::string JSON = R"JSON( // { // "a" : 1, // "b" : [] // } // )JSON"; // // bdljsn::Json json; // bdljsn::WriteOptions options; // // int rc = bdljsn::JsonUtil::read(&json, JSON); // // assert(0 == rc); //.. // There are 4 options, which can be broken down into 2 unrelated sets. // // The first set consists of the 'sortMembers' option, which controls whether // members of objects are printed in lexicogaphical order. // // The second set consists of the 'style', 'initialIndentLevel', and // 'spacesPerLevel' options - 'style' controls which format is used to render a // 'Json', and, if 'bdljsn::WriteStyle::e_PRETTY == options.style()', the // 'spacesPerLevel' and 'initialIndentLevel' options are used to control the // indentation of the output. For any other value of 'options.style()', the // 'spacesPerLevel' and 'initialIndentLevel' options have no effect. // ///'sortMembers' ///- - - - - // If 'sortMembers' is true, then the members of an object output by 'write' // will be in sorted order. Otherwise, the elements are written in an // (implementation defined) order (that may change). // // The 'sortMembers' option defaults to 'false' for performance reasons, but // applications that rely on stable output text should set 'sortMembers' to // 'true' (e.g., in a test where the resulting JSON text is compared for // equality) . // // Here, we set 'sortMembers' to 'true', and verify the resulting JSON text // matches the expected text: //.. // options.setSortMembers(true); // bsl::string output; // // rc = bdljsn::JsonUtil::write(&output, json, options); // // assert(0 == rc); // assert(R"JSON({"a":1,"b":[]})JSON" == output); //.. // Had we not specified 'setSortMembers(true)', the order of the "a" and "b" // members in the 'output' string would be unpredictable. // ///'style' And 'style'-related options /// - - - - - - - - - - - - // There are 3 options for 'style' (see 'bdljsn::WriteStyle'): //: o bdljsn::WriteStyle::e_COMPACT //: o bdljsn::WriteStyle::e_ONELINE //: o bdljsn::WriteStyle::e_PRETTY // // Next, we write 'json' using the style 'e_COMPACT' (the default), a single // line presentation with no added spaces after ':' and ',' elements. //.. // rc = bdljsn::JsonUtil::write(&output, json, options); // // assert(0 == rc); // // // Using 'e_COMPACT' style: // assert(R"JSON({"a":1,"b":[]})JSON" == output); //.. // Next, we write 'json' using the 'e_ONELINE' style, another single line // format, which adds single ' ' characters after ':' and ',' elements for // readability. //.. // options.setStyle(bdljsn::WriteStyle::e_ONELINE); // rc = bdljsn::JsonUtil::write(&output, json, options); // // assert(0 == rc); // // // Using 'e_ONELINE' style: // assert(R"JSON({"a": 1, "b": []})JSON" == output); //.. // Next, we write 'json' using the 'e_PRETTY' style, a multiline format where // newlines are introduced after each (non-terminal) '{', '[', ',', ']', and // '}' character. Furthermore, the indentation of JSON rendered in the // 'e_PRETTY' style is controlled by the other 2 attributes, 'spacesPerLevel' // and 'initialIndentLevel'. // // 'e_PRETTY' styling does not add a newline to the end of the output. // // 'spacesPerLevel' controls the number of spaces added for each successive // indentation level - e.g., if 'spacesPerLevel' is 2, then each nesting level // of the rendered JSON is indented 2 spaces. // // 'initialIndentLevel' controls how much the entire JSON output is indented. // It defaults to 0 - if it's a positive value, then the entire JSON is // indented by 'initialIndentLevel * spacesPerLevel' spaces. //.. // options.setStyle(bdljsn::WriteStyle::e_PRETTY); // options.setSpacesPerLevel(4); // the default // options.setInitialIndentLevel(0); // the default // // rc = bdljsn::JsonUtil::write(&output, json, options); // // assert(0 == rc); // // // Using 'e_PRETTY' style: // assert(R"JSON({ // "a": 1, // "b": [] // })JSON" == output); //.. // Finally, if we set 'initialIndentLevel' to 1, then an extra set of 4 spaces // is prepended to each line, where 4 is the value of 'spacesPerLevel': //.. // options.setInitialIndentLevel(1); // // rc = bdljsn::JsonUtil::write(&output, json, options); // // assert(0 == rc); // // // Using 'e_PRETTY' style (with 'initialIndentLevel' as 1): // assert(R"JSON({ // "a": 1, // "b": [] // })JSON" == output); //.. #include <bdlscm_version.h> #include <bdljsn_error.h> #include <bdljsn_json.h> #include <bdljsn_readoptions.h> #include <bdljsn_writeoptions.h> #include <bdlsb_fixedmeminstreambuf.h> #include <bslmf_movableref.h> #include <bsls_libraryfeatures.h> #include <bsl_cstdint.h> #include <bsl_iosfwd.h> #include <bsl_sstream.h> #include <bsl_string.h> #include <bsl_string_view.h> namespace BloombergLP { namespace bdljsn { // =============== // struct JsonUtil // =============== struct JsonUtil { // This 'struct' provides a namespace for utility functions that provide // 'read' and 'write' operations to/from 'Json' objects. // TYPES typedef bsl::pair<bsl::uint64_t, bsl::uint64_t> LineAndColumnNumber; // CLASS METHODS static int read(Json *result, bsl::istream& input); static int read(Json *result, bsl::istream& input, const ReadOptions& options); static int read(Json *result, bsl::streambuf *input); static int read(Json *result, bsl::streambuf *input, const ReadOptions& options); static int read(Json *result, const bsl::string_view& input); static int read(Json *result, const bsl::string_view& input, const ReadOptions& options); static int read(Json *result, Error *errorDescription, bsl::istream& input); static int read(Json *result, Error *errorDescription, bsl::istream& input, const ReadOptions& options); static int read(Json *result, Error *errorDescription, bsl::streambuf *input); static int read(Json *result, Error *errorDescription, bsl::streambuf *input, const ReadOptions& options); static int read(Json *result, Error *errorDescription, const bsl::string_view& input); static int read(Json *result, Error *errorDescription, const bsl::string_view& input, const ReadOptions& options); // Load to the specified 'result' a value-semantic representation of // the JSON text in the specified 'input'. Optionally specify an // 'errorDescription' that, if an error occurs, is loaded with a // description of the error. Optionally specify 'options' which allow // altering the maximum nesting depth. Return 0 on success, and a // non-zero value if 'input' does not consist of valid JSON text or an // error occurs when reading from 'input'. If // 'options.allowTrailingText()' is 'false' (the default), then an // error will be reported if a valid JSON text is followed by any text // that does not consist solely of white-space characters. If // 'options.allowTrailingText()' is 'true', then this function will // return success where a valid JSON document is followed by additional // text as long as that text is separated from the valid JSON by a // delimiter character (i.e., either the JSON text ends in a delimiter, // or the text that follows starts with a delimiter). Here, delimiters // are white-space characters, '[',']','{','}',',', or '"'. static bsl::ostream& printError(bsl::ostream& stream, bsl::istream& input, const Error& error); static bsl::ostream& printError(bsl::ostream& stream, bsl::streambuf *input, const Error& error); static bsl::ostream& printError(bsl::ostream& stream, const bsl::string_view& input, const Error& error); // Print, to the specified 'stream', a description of the specified // 'error', containing the line and column in the specified 'input' // where the 'error' occured. Return a reference to the modifiable // 'stream'. If 'error.location()' does not refer to a valid location // in 'input' an unspecified error description will be written to // 'stream'. Note that the caller should ensure 'input' refers to the // same input position as when 'input' was supplied to 'read' (or // whatever operation created 'error'). static int write(bsl::ostream& output, const Json& json); static int write(bsl::ostream& output, const Json& json, const WriteOptions& options); static int write(bsl::streambuf *output, const Json& json); static int write(bsl::streambuf *output, const Json& json, const WriteOptions& options); static int write(bsl::string *output, const Json& json); static int write(bsl::string *output, const Json& json, const WriteOptions& options); #if defined(BSLS_LIBRARYFEATURES_HAS_CPP17_PMR) static int write(std::pmr::string *output, const Json& json); static int write(std::pmr::string *output, const Json& json, const WriteOptions& options); #endif static int write(std::string *output, const Json& json); static int write(std::string *output, const Json& json, const WriteOptions& options); // Write to the specified 'output' a JSON text representation of the // specified 'json' document, using the optionally specified 'options' // for formatting the resulting text. Return 0 on success, and a // non-zero value otherwise. Note that this operation will report an // error only if there is an error writing to 'output'. }; // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // -------------- // class JsonUtil // -------------- // CLASS METHODS inline int JsonUtil::read(Json *result, bsl::istream& input, const ReadOptions& options) { Error tmp; return read(result, &tmp, input, options); } inline int JsonUtil::read(Json *result, bsl::istream& input) { ReadOptions options; return read(result, input, options); } inline int JsonUtil::read(Json *result, bsl::streambuf *input, const ReadOptions& options) { Error tmp; return read(result, &tmp, input, options); } inline int JsonUtil::read(Json *result, bsl::streambuf *input) { ReadOptions options; return read(result, input, options); } inline int JsonUtil::read(Json *result, const bsl::string_view& input, const ReadOptions& options) { Error tmp; return read(result, &tmp, input, options); } inline int JsonUtil::read(Json *result, const bsl::string_view& input) { ReadOptions options; return read(result, input, options); } inline int JsonUtil::read(Json *result, Error *errorDescription, bsl::istream& input, const ReadOptions& options) { return read(result, errorDescription, input.rdbuf(), options); } inline int JsonUtil::read(Json *result, Error *errorDescription, bsl::istream& input) { ReadOptions options; return read(result, errorDescription, input, options); } inline int JsonUtil::read(Json *result, Error *errorDescription, bsl::streambuf *input) { ReadOptions options; return read(result, errorDescription, input, options); } inline int JsonUtil::read(Json *result, Error *errorDescription, const bsl::string_view& input, const ReadOptions& options) { bdlsb::FixedMemInStreamBuf inputBuf(input.data(), input.size()); return read(result, errorDescription, &inputBuf, options); } inline int JsonUtil::read(Json *result, Error *errorDescription, const bsl::string_view& input) { ReadOptions options; return read(result, errorDescription, input, options); } inline bsl::ostream& JsonUtil::printError(bsl::ostream& stream, bsl::istream& input, const Error& error) { return printError(stream, input.rdbuf(), error); } inline bsl::ostream& JsonUtil::printError(bsl::ostream& stream, const bsl::string_view& input, const Error& error) { bdlsb::FixedMemInStreamBuf inputBuf(input.data(), input.size()); return printError(stream, &inputBuf, error); } inline int JsonUtil::write(bsl::streambuf *output, const Json& json, const WriteOptions& options) { bsl::ostream outputStream(output); return write(outputStream, json, options); } inline int JsonUtil::write(bsl::streambuf *output, const Json& json) { WriteOptions options; return write(output, json, options); } inline int JsonUtil::write(bsl::string *output, const Json& json, const WriteOptions& options) { bsl::ostringstream stream(output->get_allocator()); int rc = write(stream, json, options); if (0 == rc) { #ifdef BSLS_PLATFORM_CMP_SUN const bsl::string &tmpOutput = stream.str(); #else bsl::string tmpOutput = stream.str(output->get_allocator()); #endif *output = bslmf::MovableRefUtil::move(tmpOutput); } return rc; } inline int JsonUtil::write(bsl::string *output, const Json& json) { WriteOptions options; return write(output, json, options); } #if defined(BSLS_LIBRARYFEATURES_HAS_CPP17_PMR) inline int JsonUtil::write(std::pmr::string *output, const Json& json, const WriteOptions& options) { #if defined (BSLS_LIBRARYFEATURES_HAS_CPP20_BASELINE_LIBRARY) typedef std::basic_ostringstream<char, std::char_traits<char>, std::pmr::polymorphic_allocator<char> > PmrOstringStream; PmrOstringStream stream(std::ios_base::out, output->get_allocator()); int rc = write(stream, json, options); if (0 == rc) { std::pmr::string tmpOutput = stream.str(output->get_allocator()); *output = bslmf::MovableRefUtil::move(tmpOutput); } return rc; #else std::ostringstream stream; int rc = write(stream, json, options); if (0 == rc) { const bsl::string &tmpOutput = stream.str(); *output = bslmf::MovableRefUtil::move(tmpOutput); } return rc; #endif } #endif // defined(BSLS_LIBRARYFEATURES_HAS_CPP17_PMR) #if defined(BSLS_LIBRARYFEATURES_HAS_CPP17_PMR) inline int JsonUtil::write(std::pmr::string *output, const Json& json) { WriteOptions options; return write(output, json, options); } #endif // defined(BSLS_LIBRARYFEATURES_HAS_CPP17_PMR) inline int JsonUtil::write(std::string *output, const Json& json, const WriteOptions& options) { std::ostringstream stream; int rc = write(stream, json, options); if (0 == rc) { std::string tmpOutput(stream.str()); *output = bslmf::MovableRefUtil::move(tmpOutput); } return rc; } inline int JsonUtil::write(std::string *output, const Json& json) { WriteOptions options; return write(output, json, options); } inline int JsonUtil::write(bsl::ostream& output, const Json& json) { WriteOptions options; return write(output, json, options); } } // close package namespace } // close enterprise namespace #endif // INCLUDED_BDLJSN_JSONUTIL // ---------------------------------------------------------------------------- // Copyright 2022 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------