// 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`:
// * `read`  populate a `Json` object from a JSON text document.
// * `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`:
// * `escapeForwardSlash`: determines whether any `/` characters are output
//   escaped (as `\/`) or not (as `/`) in names or strings.
// * `sortMembers`: sort the members of any Object elements in the output JSON
//   (default: `false`)
// * `style`: the style of the resulting output
//   - `e_COMPACT` (default): render with no added white space
//   - `e_ONELINE`: render a human readable single-line format (e.g., for
//     logging)
//   - `e_PRETTY`: render a multi-line human readable format
// * `spacesPerLevel`: for the `e_PRETTY` style, the number of spaces added
//   for each additional nesting level (default 4)
// * `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 double-quotes.  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 lexicographical order.
//
// The second set consists of the `style`, `initialIndentLevel`, and
// `spacesPerLevel` options - `style` controls the format 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`):
// * bdljsn::WriteStyle::e_COMPACT
// * bdljsn::WriteStyle::e_ONELINE
// * 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
                              // ===============

/// This `struct` provides a namespace for utility functions that provide
/// `read` and `write` operations to/from `Json` objects.
struct JsonUtil {

    // TYPES
    typedef bsl::pair<bsl::uint64_t, bsl::uint64_t> LineAndColumnNumber;

    // CLASS METHODS

    /// 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 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);

    /// Print, to the specified `stream`, a description of the specified
    /// `error`, containing the line and column in the specified `input`
    /// where the `error` occurred.  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 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);

    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);
#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR_STRING
    static int write(std::pmr::string    *output,
                     const Json&          json);
    static int write(std::pmr::string    *output,
                     const Json&          json,
                     const WriteOptions&  options);
#endif
    /// 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`.
    static int write(std::string         *output,
                     const Json&          json);
    static int write(std::string         *output,
                     const Json&          json,
                     const WriteOptions&  options);
};

// ============================================================================
//                            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);
}

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR_STRING
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_STRING)

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR_STRING
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_STRING)

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 ----------------------------------
