Skip to content
Snippets Groups Projects
json.hpp 363 KiB
Newer Older
    m_value.array->push_back(val);
  }
  /*!
  @brief add an object to an array
  @copydoc push_back(basic_json&&)
  */
  reference operator+=(const basic_json &val)
  {
    push_back(val);
    return *this;
  }
  /*!
  @brief add an object to an object
  Inserts the given element @a val to the JSON object. If the function is
  called on a JSON null value, an empty object is created before inserting
  @a val.
  @param[in] val the value to add to the JSON object
  @throw std::domain_error when called on a type other than JSON object or
  null; example: `"cannot use push_back() with number"`
  @complexity Logarithmic in the size of the container, O(log(`size()`)).
  @liveexample{The example shows how `push_back()` and `+=` can be used to
  add elements to a JSON object. Note how the `null` value was silently
  converted to a JSON object.,push_back__object_t__value}

  @since version 1.0.0
  */
  void push_back(const typename object_t::value_type &val)
  {
    // push_back only works for null objects or objects
    if (not(is_null() or is_object()))
      JSON_THROW(
          std::domain_error("cannot use push_back() with " + type_name()));
    }
    // transform null object into an object
    if (is_null())
    {
      m_type = value_t::object;
      m_value = value_t::object;
      assert_invariant();
    // add element to array
    m_value.object->insert(val);
  }
  /*!
  @brief add an object to an object
  @copydoc push_back(const typename object_t::value_type&)
  */
  reference operator+=(const typename object_t::value_type &val)
  {
    push_back(val);
    return *this;
  }
  /*!
  @brief add an object to an object
  This function allows to use `push_back` with an initializer list. In case
  1. the current value is an object,
  2. the initializer list @a init contains only two elements, and
  3. the first element of @a init is a string,
  @a init is converted into an object element and added using
  @ref push_back(const typename object_t::value_type&). Otherwise, @a init
  is converted to a JSON value and added using @ref push_back(basic_json&&).
  @param init  an initializer list
  @complexity Linear in the size of the initializer list @a init.
  @note This function is required to resolve an ambiguous overload error,
        because pairs like `{"key", "value"}` can be both interpreted as
        `object_t::value_type` or `std::initializer_list<basic_json>`, see
        https://github.com/nlohmann/json/issues/235 for more information.
  @liveexample{The example shows how initializer lists are treated as
  objects when possible.,push_back__initializer_list}
  */
  void push_back(std::initializer_list<basic_json> init)
  {
    if (is_object() and init.size() == 2 and init.begin()->is_string())
      const string_t key = *init.begin();
      push_back(typename object_t::value_type(key, *(init.begin() + 1)));
    else
    {
      push_back(basic_json(init));
    }
  }
  /*!
  @brief add an object to an object
  @copydoc push_back(std::initializer_list<basic_json>)
  */
  reference operator+=(std::initializer_list<basic_json> init)
  {
    push_back(init);
    return *this;
  }
  /*!
  @brief add an object to an array
  Creates a JSON value from the passed parameters @a args to the end of the
  JSON value. If the function is called on a JSON null value, an empty array
  is created before appending the value created from @a args.
  @param[in] args arguments to forward to a constructor of @ref basic_json
  @tparam Args compatible types to create a @ref basic_json object
  @throw std::domain_error when called on a type other than JSON array or
  null; example: `"cannot use emplace_back() with number"`
  @complexity Amortized constant.
  @liveexample{The example shows how `push_back()` can be used to add
  elements to a JSON array. Note how the `null` value was silently converted
  to a JSON array.,emplace_back}
  @since version 2.0.8
  */
  template <class... Args> void emplace_back(Args &&... args)
  {
    // emplace_back only works for null objects or arrays
    if (not(is_null() or is_array()))
      JSON_THROW(
          std::domain_error("cannot use emplace_back() with " + type_name()));
    // transform null object into an array
    if (is_null())
    {
      m_type = value_t::array;
      m_value = value_t::array;
      assert_invariant();
    }
    // add element to array (perfect forwarding)
    m_value.array->emplace_back(std::forward<Args>(args)...);
  }
  /*!
  @brief add an object to an object if key does not exist
  Inserts a new element into a JSON object constructed in-place with the
  given @a args if there is no element with the key in the container. If the
  function is called on a JSON null value, an empty object is created before
  appending the value created from @a args.
  @param[in] args arguments to forward to a constructor of @ref basic_json
  @tparam Args compatible types to create a @ref basic_json object
  @return a pair consisting of an iterator to the inserted element, or the
          already-existing element if no insertion happened, and a bool
          denoting whether the insertion took place.
  @throw std::domain_error when called on a type other than JSON object or
  null; example: `"cannot use emplace() with number"`
  @complexity Logarithmic in the size of the container, O(log(`size()`)).
  @liveexample{The example shows how `emplace()` can be used to add elements
  to a JSON object. Note how the `null` value was silently converted to a
  JSON object. Further note how no value is added if there was already one
  value stored with the same key.,emplace}
  @since version 2.0.8
  */
  template <class... Args> std::pair<iterator, bool> emplace(Args &&... args)
  {
    // emplace only works for null objects or arrays
    if (not(is_null() or is_object()))
      JSON_THROW(std::domain_error("cannot use emplace() with " + type_name()));
    // transform null object into an object
    if (is_null())
    {
      m_type = value_t::object;
      m_value = value_t::object;
      assert_invariant();
    }
    // add element to array (perfect forwarding)
    auto res = m_value.object->emplace(std::forward<Args>(args)...);
    // create result iterator and set iterator to the result of emplace
    auto it = begin();
    it.m_it.object_iterator = res.first;
    // return pair of iterator and boolean
    return {it, res.second};
  }
  /*!
  @brief inserts element
  Inserts element @a val before iterator @a pos.
  @param[in] pos iterator before which the content will be inserted; may be
  the end() iterator
  @param[in] val element to insert
  @return iterator pointing to the inserted @a val.
  @throw std::domain_error if called on JSON values other than arrays;
  example: `"cannot use insert() with string"`
  @throw std::domain_error if @a pos is not an iterator of *this; example:
  `"iterator does not fit current value"`
  @complexity Constant plus linear in the distance between pos and end of
  the container.
  @liveexample{The example shows how `insert()` is used.,insert}
  @since version 1.0.0
  */
  iterator insert(const_iterator pos, const basic_json &val)
  {
    // insert only works for arrays
    if (is_array())
      // check if iterator pos fits to this JSON value
      if (pos.m_object != this)
      {
        JSON_THROW(std::domain_error("iterator does not fit current value"));
      }
      // insert to array and return iterator
      iterator result(this);
      result.m_it.array_iterator =
          m_value.array->insert(pos.m_it.array_iterator, val);
      return result;
    }
    JSON_THROW(std::domain_error("cannot use insert() with " + type_name()));
  }
  /*!
  @brief inserts element
  @copydoc insert(const_iterator, const basic_json&)
  */
  iterator insert(const_iterator pos, basic_json &&val)
  {
    return insert(pos, val);
  }
  /*!
  @brief inserts elements
  Inserts @a cnt copies of @a val before iterator @a pos.
  @param[in] pos iterator before which the content will be inserted; may be
  the end() iterator
  @param[in] cnt number of copies of @a val to insert
  @param[in] val element to insert
  @return iterator pointing to the first element inserted, or @a pos if
  `cnt==0`
  @throw std::domain_error if called on JSON values other than arrays;
  example: `"cannot use insert() with string"`
  @throw std::domain_error if @a pos is not an iterator of *this; example:
  `"iterator does not fit current value"`
  @complexity Linear in @a cnt plus linear in the distance between @a pos
  and end of the container.
  @liveexample{The example shows how `insert()` is used.,insert__count}
  @since version 1.0.0
  */
  iterator insert(const_iterator pos, size_type cnt, const basic_json &val)
  {
    // insert only works for arrays
    if (is_array())
      // check if iterator pos fits to this JSON value
      if (pos.m_object != this)
      {
        JSON_THROW(std::domain_error("iterator does not fit current value"));
      }
      // insert to array and return iterator
      iterator result(this);
      result.m_it.array_iterator =
          m_value.array->insert(pos.m_it.array_iterator, cnt, val);
      return result;
    JSON_THROW(std::domain_error("cannot use insert() with " + type_name()));
  }
  /*!
  @brief inserts elements
  Inserts elements from range `[first, last)` before iterator @a pos.
  @param[in] pos iterator before which the content will be inserted; may be
  the end() iterator
  @param[in] first begin of the range of elements to insert
  @param[in] last end of the range of elements to insert
  @throw std::domain_error if called on JSON values other than arrays;
  example: `"cannot use insert() with string"`
  @throw std::domain_error if @a pos is not an iterator of *this; example:
  `"iterator does not fit current value"`
  @throw std::domain_error if @a first and @a last do not belong to the same
  JSON value; example: `"iterators do not fit"`
  @throw std::domain_error if @a first or @a last are iterators into
  container for which insert is called; example: `"passed iterators may not
  belong to container"`
  @return iterator pointing to the first element inserted, or @a pos if
  `first==last`
  @complexity Linear in `std::distance(first, last)` plus linear in the
  distance between @a pos and end of the container.
  @liveexample{The example shows how `insert()` is used.,insert__range}
  @since version 1.0.0
  */
  iterator insert(const_iterator pos, const_iterator first, const_iterator last)
  {
    // insert only works for arrays
    if (not is_array())
      JSON_THROW(std::domain_error("cannot use insert() with " + type_name()));
    // check if iterator pos fits to this JSON value
    if (pos.m_object != this)
    {
      JSON_THROW(std::domain_error("iterator does not fit current value"));
    }
    // check if range iterators belong to the same JSON object
    if (first.m_object != last.m_object)
    {
      JSON_THROW(std::domain_error("iterators do not fit"));
    if (first.m_object == this or last.m_object == this)
    {
      JSON_THROW(
          std::domain_error("passed iterators may not belong to container"));
    }
    // insert to array and return iterator
    iterator result(this);
    result.m_it.array_iterator = m_value.array->insert(
        pos.m_it.array_iterator, first.m_it.array_iterator,
        last.m_it.array_iterator);
    return result;
  }
  /*!
  @brief inserts elements
  Inserts elements from initializer list @a ilist before iterator @a pos.
  @param[in] pos iterator before which the content will be inserted; may be
  the end() iterator
  @param[in] ilist initializer list to insert the values from
  @throw std::domain_error if called on JSON values other than arrays;
  example: `"cannot use insert() with string"`
  @throw std::domain_error if @a pos is not an iterator of *this; example:
  `"iterator does not fit current value"`
  @return iterator pointing to the first element inserted, or @a pos if
  `ilist` is empty
  @complexity Linear in `ilist.size()` plus linear in the distance between
  @a pos and end of the container.
  @liveexample{The example shows how `insert()` is used.,insert__ilist}
  @since version 1.0.0
  */
  iterator insert(const_iterator pos, std::initializer_list<basic_json> ilist)
  {
    // insert only works for arrays
    if (not is_array())
      JSON_THROW(std::domain_error("cannot use insert() with " + type_name()));
    // check if iterator pos fits to this JSON value
    if (pos.m_object != this)
      JSON_THROW(std::domain_error("iterator does not fit current value"));
    // insert to array and return iterator
    iterator result(this);
    result.m_it.array_iterator =
        m_value.array->insert(pos.m_it.array_iterator, ilist);
    return result;
  }
  /*!
  @brief exchanges the values
  Exchanges the contents of the JSON value with those of @a other. Does not
  invoke any move, copy, or swap operations on individual elements. All
  iterators and references remain valid. The past-the-end iterator is
  invalidated.
  @param[in,out] other JSON value to exchange the contents with
  @complexity Constant.
  @liveexample{The example below shows how JSON values can be swapped with
  `swap()`.,swap__reference}
  @since version 1.0.0
  */
  void swap(reference other) noexcept(
      std::is_nothrow_move_constructible<value_t>::value
          and std::is_nothrow_move_assignable<value_t>::value
              and std::is_nothrow_move_constructible<json_value>::value
                  and std::is_nothrow_move_assignable<json_value>::value)
  {
    std::swap(m_type, other.m_type);
    std::swap(m_value, other.m_value);
    assert_invariant();
  }
  /*!
  @brief exchanges the values
  Exchanges the contents of a JSON array with those of @a other. Does not
  invoke any move, copy, or swap operations on individual elements. All
  iterators and references remain valid. The past-the-end iterator is
  invalidated.
  @param[in,out] other array to exchange the contents with
  @throw std::domain_error when JSON value is not an array; example:
  `"cannot use swap() with string"`
  @complexity Constant.
  @liveexample{The example below shows how arrays can be swapped with
  `swap()`.,swap__array_t}
  @since version 1.0.0
  */
  void swap(array_t &other)
  {
    // swap only works for arrays
    if (is_array())
      std::swap(*(m_value.array), other);
    else
    {
      JSON_THROW(std::domain_error("cannot use swap() with " + type_name()));
    }
  }
  /*!
  @brief exchanges the values
  Exchanges the contents of a JSON object with those of @a other. Does not
  invoke any move, copy, or swap operations on individual elements. All
  iterators and references remain valid. The past-the-end iterator is
  invalidated.
  @param[in,out] other object to exchange the contents with
  @throw std::domain_error when JSON value is not an object; example:
  `"cannot use swap() with string"`
  @complexity Constant.
  @liveexample{The example below shows how objects can be swapped with
  `swap()`.,swap__object_t}
  @since version 1.0.0
  */
  void swap(object_t &other)
  {
    // swap only works for objects
    if (is_object())
      std::swap(*(m_value.object), other);
      JSON_THROW(std::domain_error("cannot use swap() with " + type_name()));
  /*!
  @brief exchanges the values
  Exchanges the contents of a JSON string with those of @a other. Does not
  invoke any move, copy, or swap operations on individual elements. All
  iterators and references remain valid. The past-the-end iterator is
  invalidated.
  @param[in,out] other string to exchange the contents with
  @throw std::domain_error when JSON value is not a string; example: `"cannot
  use swap() with boolean"`
  @complexity Constant.
  @liveexample{The example below shows how strings can be swapped with
  `swap()`.,swap__string_t}
  @since version 1.0.0
  */
  void swap(string_t &other)
  {
    // swap only works for strings
    if (is_string())
      std::swap(*(m_value.string), other);
      JSON_THROW(std::domain_error("cannot use swap() with " + type_name()));
  //////////////////////////////////////////
  // lexicographical comparison operators //
  //////////////////////////////////////////
  /// @name lexicographical comparison operators
  /// @{
private:
  /*!
  @brief comparison operator for JSON types
  Returns an ordering that is similar to Python:
  - order: null < boolean < number < object < array < string
  - furthermore, each type is not smaller than itself
  @since version 1.0.0
  */
  friend bool operator<(const value_t lhs, const value_t rhs) noexcept
  {
    static constexpr std::array<uint8_t, 8> order = {{
        0, // null
        3, // object
        4, // array
        5, // string
        1, // boolean
        2, // integer
        2, // unsigned
        2, // float
    }};
    // discarded values are not comparable
    if (lhs == value_t::discarded or rhs == value_t::discarded)
      return false;
    }
    return order[static_cast<std::size_t>(lhs)] <
           order[static_cast<std::size_t>(rhs)];
  }
public:
  /*!
  @brief comparison: equal
  Compares two JSON values for equality according to the following rules:
  - Two JSON values are equal if (1) they are from the same type and (2)
    their stored values are the same.
  - Integer and floating-point numbers are automatically converted before
    comparison. Floating-point numbers are compared indirectly: two
    floating-point numbers `f1` and `f2` are considered equal if neither
    `f1 > f2` nor `f2 > f1` holds.
  - Two JSON null values are equal.
  @param[in] lhs  first JSON value to consider
  @param[in] rhs  second JSON value to consider
  @return whether the values @a lhs and @a rhs are equal
  @complexity Linear.
  @liveexample{The example demonstrates comparing several JSON
  types.,operator__equal}
  @since version 1.0.0
  */
  friend bool operator==(const_reference lhs, const_reference rhs) noexcept
  {
    const auto lhs_type = lhs.type();
    const auto rhs_type = rhs.type();
    if (lhs_type == rhs_type)
      switch (lhs_type)
      {
        case value_t::array:
        {
          return *lhs.m_value.array == *rhs.m_value.array;
        }
        case value_t::object:
        {
          return *lhs.m_value.object == *rhs.m_value.object;
        }
        case value_t::null:
          return true;
        case value_t::string:
        {
          return *lhs.m_value.string == *rhs.m_value.string;
        }
        case value_t::boolean:
        {
          return lhs.m_value.boolean == rhs.m_value.boolean;
        }
        case value_t::number_integer:
        {
          return lhs.m_value.number_integer == rhs.m_value.number_integer;
        }
        case value_t::number_unsigned:
        {
          return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned;
        }
        case value_t::number_float:
        {
          return lhs.m_value.number_float == rhs.m_value.number_float;
        }
        default:
        {
          return false;
        }
      }
    }
    else if (lhs_type == value_t::number_integer and
             rhs_type == value_t::number_float)
    {
      return static_cast<number_float_t>(lhs.m_value.number_integer) ==
             rhs.m_value.number_float;
    }
    else if (lhs_type == value_t::number_float and
             rhs_type == value_t::number_integer)
    {
      return lhs.m_value.number_float ==
             static_cast<number_float_t>(rhs.m_value.number_integer);
    }
    else if (lhs_type == value_t::number_unsigned and
             rhs_type == value_t::number_float)
    {
      return static_cast<number_float_t>(lhs.m_value.number_unsigned) ==
             rhs.m_value.number_float;
    }
    else if (lhs_type == value_t::number_float and
             rhs_type == value_t::number_unsigned)
    {
      return lhs.m_value.number_float ==
             static_cast<number_float_t>(rhs.m_value.number_unsigned);
    }
    else if (lhs_type == value_t::number_unsigned and
             rhs_type == value_t::number_integer)
    {
      return static_cast<number_integer_t>(lhs.m_value.number_unsigned) ==
             rhs.m_value.number_integer;
    }
    else if (lhs_type == value_t::number_integer and
             rhs_type == value_t::number_unsigned)
    {
      return lhs.m_value.number_integer ==
             static_cast<number_integer_t>(rhs.m_value.number_unsigned);
    }
    return false;
  }
  /*!
  @brief comparison: equal
  The functions compares the given JSON value against a null pointer. As the
  null pointer can be used to initialize a JSON value to null, a comparison
  of JSON value @a v with a null pointer should be equivalent to call
  `v.is_null()`.
  @param[in] v  JSON value to consider
  @return whether @a v is null
  @complexity Constant.
  @liveexample{The example compares several JSON types to the null pointer.
  ,operator__equal__nullptr_t}
  @since version 1.0.0
  */
  friend bool operator==(const_reference v, std::nullptr_t) noexcept
  {
    return v.is_null();
  }
  /*!
  @brief comparison: equal
  @copydoc operator==(const_reference, std::nullptr_t)
  */
  friend bool operator==(std::nullptr_t, const_reference v) noexcept
  {
    return v.is_null();
  }
  /*!
  @brief comparison: not equal
  Compares two JSON values for inequality by calculating `not (lhs == rhs)`.
  @param[in] lhs  first JSON value to consider
  @param[in] rhs  second JSON value to consider
  @return whether the values @a lhs and @a rhs are not equal
  @complexity Linear.
  @liveexample{The example demonstrates comparing several JSON
  types.,operator__notequal}
  @since version 1.0.0
  */
  friend bool operator!=(const_reference lhs, const_reference rhs) noexcept
  {
    return not(lhs == rhs);
  }
  /*!
  @brief comparison: not equal
  The functions compares the given JSON value against a null pointer. As the
  null pointer can be used to initialize a JSON value to null, a comparison
  of JSON value @a v with a null pointer should be equivalent to call
  `not v.is_null()`.
  @param[in] v  JSON value to consider
  @return whether @a v is not null
  @complexity Constant.
  @liveexample{The example compares several JSON types to the null pointer.
  ,operator__notequal__nullptr_t}
  @since version 1.0.0
  */
  friend bool operator!=(const_reference v, std::nullptr_t) noexcept
  {
    return not v.is_null();
  }
  /*!
  @brief comparison: not equal
  @copydoc operator!=(const_reference, std::nullptr_t)
  */
  friend bool operator!=(std::nullptr_t, const_reference v) noexcept
  {
    return not v.is_null();
  }
  /*!
  @brief comparison: less than
  Compares whether one JSON value @a lhs is less than another JSON value @a
  rhs according to the following rules:
  - If @a lhs and @a rhs have the same type, the values are compared using
    the default `<` operator.
  - Integer and floating-point numbers are automatically converted before
    comparison
  - In case @a lhs and @a rhs have different types, the values are ignored
    and the order of the types is considered, see
    @ref operator<(const value_t, const value_t).
  @param[in] lhs  first JSON value to consider
  @param[in] rhs  second JSON value to consider
  @return whether @a lhs is less than @a rhs
  @complexity Linear.
  @liveexample{The example demonstrates comparing several JSON
  types.,operator__less}
  @since version 1.0.0
  */
  friend bool operator<(const_reference lhs, const_reference rhs) noexcept
  {
    const auto lhs_type = lhs.type();
    const auto rhs_type = rhs.type();

    if (lhs_type == rhs_type)
      switch (lhs_type)
      {
        case value_t::array:
          return *lhs.m_value.array < *rhs.m_value.array;
        case value_t::object:
        {
          return *lhs.m_value.object < *rhs.m_value.object;
        }
        case value_t::null:
        {
          return false;
        }
        case value_t::string:
        {
          return *lhs.m_value.string < *rhs.m_value.string;
        }
        case value_t::boolean:
        {
          return lhs.m_value.boolean < rhs.m_value.boolean;
        }
        case value_t::number_integer:
        {
          return lhs.m_value.number_integer < rhs.m_value.number_integer;
        }
        case value_t::number_unsigned:
        {
          return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned;
        }
        case value_t::number_float:
        {
          return lhs.m_value.number_float < rhs.m_value.number_float;
        }
        default:
          return false;
      }
    }
    else if (lhs_type == value_t::number_integer and
             rhs_type == value_t::number_float)
    {
      return static_cast<number_float_t>(lhs.m_value.number_integer) <
             rhs.m_value.number_float;
    }
    else if (lhs_type == value_t::number_float and
             rhs_type == value_t::number_integer)
    {
      return lhs.m_value.number_float <
             static_cast<number_float_t>(rhs.m_value.number_integer);
    }
    else if (lhs_type == value_t::number_unsigned and
             rhs_type == value_t::number_float)
    {
      return static_cast<number_float_t>(lhs.m_value.number_unsigned) <
             rhs.m_value.number_float;
    }
    else if (lhs_type == value_t::number_float and
             rhs_type == value_t::number_unsigned)
    {
      return lhs.m_value.number_float <
             static_cast<number_float_t>(rhs.m_value.number_unsigned);
    }
    else if (lhs_type == value_t::number_integer and
             rhs_type == value_t::number_unsigned)
    {
      return lhs.m_value.number_integer <
             static_cast<number_integer_t>(rhs.m_value.number_unsigned);
    }
    else if (lhs_type == value_t::number_unsigned and
             rhs_type == value_t::number_integer)
    {
      return static_cast<number_integer_t>(lhs.m_value.number_unsigned) <
             rhs.m_value.number_integer;
    // We only reach this line if we cannot compare values. In that case,
    // we compare types. Note we have to call the operator explicitly,
    // because MSVC has problems otherwise.
    return operator<(lhs_type, rhs_type);
  }
  /*!
  @brief comparison: less than or equal
  Compares whether one JSON value @a lhs is less than or equal to another
  JSON value by calculating `not (rhs < lhs)`.
  @param[in] lhs  first JSON value to consider
  @param[in] rhs  second JSON value to consider
  @return whether @a lhs is less than or equal to @a rhs
  @complexity Linear.
  @liveexample{The example demonstrates comparing several JSON
  types.,operator__greater}
  @since version 1.0.0
  */
  friend bool operator<=(const_reference lhs, const_reference rhs) noexcept
  {
    return not(rhs < lhs);
  }
  /*!
  @brief comparison: greater than
  Compares whether one JSON value @a lhs is greater than another
  JSON value by calculating `not (lhs <= rhs)`.
  @param[in] lhs  first JSON value to consider
  @param[in] rhs  second JSON value to consider
  @return whether @a lhs is greater than to @a rhs
  @complexity Linear.
  @liveexample{The example demonstrates comparing several JSON
  types.,operator__lessequal}
  @since version 1.0.0
  */
  friend bool operator>(const_reference lhs, const_reference rhs) noexcept
  {
    return not(lhs <= rhs);
  }
  /*!
  @brief comparison: greater than or equal
  Compares whether one JSON value @a lhs is greater than or equal to another
  JSON value by calculating `not (lhs < rhs)`.
  @param[in] lhs  first JSON value to consider
  @param[in] rhs  second JSON value to consider
  @return whether @a lhs is greater than or equal to @a rhs
  @complexity Linear.
  @liveexample{The example demonstrates comparing several JSON
  types.,operator__greaterequal}
  @since version 1.0.0
  */
  friend bool operator>=(const_reference lhs, const_reference rhs) noexcept
  {
    return not(lhs < rhs);
  }
  ///////////////////
  // serialization //
  ///////////////////
  /// @name serialization
  /// @{
  /*!
  @brief serialize to stream
  Serialize the given JSON value @a j to the output stream @a o. The JSON
  value will be serialized using the @ref dump member function. The
  indentation of the output can be controlled with the member variable
  `width` of the output stream @a o. For instance, using the manipulator
  `std::setw(4)` on @a o sets the indentation level to `4` and the
  serialization result is the same as calling `dump(4)`.
  @note During serializaion, the locale and the precision of the output
  stream @a o are changed. The original values are restored when the
  function returns.
  @param[in,out] o  stream to serialize to
  @param[in] j  JSON value to serialize
  @return the stream @a o
  @complexity Linear.
  @liveexample{The example below shows the serialization with different
  parameters to `width` to adjust the indentation level.,operator_serialize}
  @since version 1.0.0
  */
  friend std::ostream &operator<<(std::ostream &o, const basic_json &j)
  {
    // read width member and use it as indentation parameter if nonzero
    const bool pretty_print = (o.width() > 0);
    const auto indentation = (pretty_print ? o.width() : 0);
    // reset width to 0 for subsequent calls to this stream