diff --git a/src/json.hpp b/src/json.hpp index 64877d36d6..90941b71c7 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -124,6 +124,8 @@ default; will be used in @ref string_t) in @ref boolean_t) @tparam NumberIntegerType type for JSON integer numbers (@c `int64_t` by default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c `uint64_t` by +default; will be used in @ref number_unsigned_t) @tparam NumberFloatType type for JSON floating-point numbers (@c `double` by default; will be used in @ref number_float_t) @tparam AllocatorType type of the allocator to use (@c `std::allocator` by @@ -185,6 +187,7 @@ template < class StringType = std::string, class BooleanType = bool, class NumberIntegerType = int64_t, + class NumberUnsignedType = uint64_t, class NumberFloatType = double, template class AllocatorType = std::allocator > @@ -197,6 +200,7 @@ class basic_json StringType, BooleanType, NumberIntegerType, + NumberUnsignedType, NumberFloatType, AllocatorType>; @@ -475,9 +479,10 @@ class basic_json > permitted. This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is an - integer or a floating-point number. Therefore, two different types, @ref - number_integer_t and @ref number_float_t are used. + C++ allows more precise storage if it is known whether the number is a + signed integer, an unsigned integer or a floating-point number. Therefore, + three different types, @ref number_integer_t, @ref number_unsigned_t and + @ref number_float_t are used. To store integer numbers in C++, a type is defined by the template parameter @a NumberIntegerType which chooses the type to use. @@ -510,7 +515,7 @@ class basic_json that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers that are out of range will yield over/underflow when used in a constructor. During deserialization, too large or small integer numbers will be - automatically be stored as @ref number_float_t. + automatically be stored as @ref number_unsigned_t or @ref number_float_t. [RFC 7159](http://rfc7159.net/rfc7159) further states: > Note that when such software is used, numbers that are integers and are @@ -526,10 +531,84 @@ class basic_json @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + @since version 1.0.0 */ using number_integer_t = NumberIntegerType; + /*! + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most programming + > languages. A number is represented in base 10 using decimal digits. It + > contains an integer component that may be prefixed with an optional minus + > sign, which may be followed by a fraction part and/or an exponent part. + > Leading zeros are not allowed. (...) Numeric values that cannot be + > represented in the grammar below (such as Infinity and NaN) are not + > permitted. + + This description includes both integer and floating-point numbers. However, + C++ allows more precise storage if it is known whether the number is a + signed integer, an unsigned integer or a floating-point number. Therefore, + three different types, @ref number_integer_t, @ref number_unsigned_t and + @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the template + parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the default + value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer number + that can be stored is `0`. Integer numbers + that are out of range will yield over/underflow when used in a constructor. + During deserialization, too large or small integer numbers will be + automatically be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], this + class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @sa @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + /*! @brief a type for a number (floating-point) @@ -543,9 +622,10 @@ class basic_json > permitted. This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is an - integer or a floating-point number. Therefore, two different types, @ref - number_integer_t and @ref number_float_t are used. + C++ allows more precise storage if it is known whether the number is a + signed integer, an unsigned integer or a floating-point number. Therefore, + three different types, @ref number_integer_t, @ref number_unsigned_t and + @ref number_float_t are used. To store floating-point numbers in C++, a type is defined by the template parameter @a NumberFloatType which chooses the type to use. @@ -591,6 +671,8 @@ class basic_json @sa @ref number_integer_t -- type for number values (integer) + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + @since version 1.0.0 */ using number_float_t = NumberFloatType; @@ -620,6 +702,7 @@ class basic_json string, ///< string value boolean, ///< boolean value number_integer, ///< number value (integer) + number_unsigned,///< number value (unsigned integer) number_float, ///< number value (floating-point) discarded ///< discarded by the the parser callback function }; @@ -663,6 +746,8 @@ class basic_json boolean_t boolean; /// number (integer) number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; /// number (floating-point) number_float_t number_float; @@ -672,6 +757,8 @@ class basic_json json_value(boolean_t v) noexcept : boolean(v) {} /// constructor for numbers (integer) json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} /// constructor for numbers (floating-point) json_value(number_float_t v) noexcept : number_float(v) {} /// constructor for empty values of a given type @@ -708,6 +795,12 @@ class basic_json number_integer = number_integer_t(0); break; } + + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } case value_t::number_float: { @@ -864,6 +957,8 @@ class basic_json (floating-point) value @sa @ref basic_json(const number_integer_t) -- create a number (integer) value + @sa @ref basic_json(const number_unsigned_t) -- create a number (unsigned) + value @since version 1.0.0 */ @@ -1165,7 +1260,8 @@ class basic_json typename std::enable_if< not (std::is_same::value) and std::is_same::value - , int>::type = 0> + , int>::type + = 0> basic_json(const number_integer_t val) : m_type(value_t::number_integer), m_value(val) {} @@ -1228,13 +1324,74 @@ class basic_json template::value and - std::numeric_limits::is_integer, CompatibleNumberIntegerType>::type + std::numeric_limits::is_integer and + std::numeric_limits::is_signed, + CompatibleNumberIntegerType>::type = 0> basic_json(const CompatibleNumberIntegerType val) noexcept : m_type(value_t::number_integer), m_value(static_cast(val)) {} + /*! + @brief create an unsigned integer number (explicit) + + Create an unsigned integer number JSON value with a given content. + + @tparam T helper type to compare number_unsigned_t and unsigned int + (not visible in) the interface. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @sa @ref basic_json(const CompatibleNumberUnsignedType) -- create a number + value (unsigned integer) from a compatible number type + + @since version 2.0.0 + */ + template::value) + and std::is_same::value + , int>::type + = 0> + basic_json(const number_unsigned_t val) + : m_type(value_t::number_unsigned), m_value(val) + {} + + /*! + @brief create an unsigned number (implicit) + + Create an unsigned number JSON value with a given content. This constructor + allows any type that can be used to construct values of type @ref + number_unsigned_t. Examples may include the types `unsigned int`, `uint32_t`, + or `unsigned short`. + + @tparam CompatibleNumberUnsignedType an integer type which is compatible to + @ref number_unsigned_t. + + @param[in] val an unsigned integer to create a JSON number from + + @complexity Constant. + + @sa @ref basic_json(const number_unsigned_t) -- create a number value + (unsigned) + + @since version 2.0.0 + */ + template::value and + std::numeric_limits::is_integer and + !std::numeric_limits::is_signed, + CompatibleNumberUnsignedType>::type + = 0> + basic_json(const CompatibleNumberUnsignedType val) noexcept + : m_type(value_t::number_unsigned), + m_value(static_cast(val)) + {} + /*! @brief create a floating-point number (explicit) @@ -1594,6 +1751,7 @@ class basic_json case value_t::boolean: case value_t::number_float: case value_t::number_integer: + case value_t::number_unsigned: case value_t::string: { if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) @@ -1617,6 +1775,13 @@ class basic_json m_value.number_integer = first.m_object->m_value.number_integer; break; } + + case value_t::number_unsigned: + { + assert(first.m_object != nullptr); + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } case value_t::number_float: { @@ -1720,6 +1885,12 @@ class basic_json m_value = other.m_value.number_integer; break; } + + case value_t::number_unsigned: + { + m_value = other.m_value.number_unsigned; + break; + } case value_t::number_float: { @@ -1998,15 +2169,17 @@ class basic_json This function returns true iff the JSON value is a number. This includes both integer and floating-point values. - @return `true` if type is number (regardless whether integer or - floating-type), `false` otherwise. + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. @complexity Constant. @liveexample{The following code exemplifies @ref is_number for all JSON types.,is_number} - @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer number @sa @ref is_number_float() -- check if value is a floating-point number @since version 1.0.0 @@ -2019,10 +2192,11 @@ class basic_json /*! @brief return whether value is an integer number - This function returns true iff the JSON value is an integer number. This - excludes floating-point values. + This function returns true iff the JSON value is an integer or unsigned + integer number. This excludes floating-point values. - @return `true` if type is an integer number, `false` otherwise. + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. @complexity Constant. @@ -2030,20 +2204,43 @@ class basic_json JSON types.,is_number_integer} @sa @ref is_number() -- check if value is a number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer number @sa @ref is_number_float() -- check if value is a floating-point number @since version 1.0.0 */ bool is_number_integer() const noexcept { - return m_type == value_t::number_integer; + return m_type == value_t::number_integer or m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true iff the JSON value is an unsigned integer number. + This excludes floating-point and (signed) integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + bool is_number_unsigned() const noexcept + { + return m_type == value_t::number_unsigned; } /*! @brief return whether value is a floating-point number This function returns true iff the JSON value is a floating-point number. - This excludes integer values. + This excludes integer and unsigned integer values. @return `true` if type is a floating-point number, `false` otherwise. @@ -2054,6 +2251,7 @@ class basic_json @sa @ref is_number() -- check if value is number @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer number @since version 1.0.0 */ @@ -2321,6 +2519,11 @@ class basic_json { return static_cast(m_value.number_integer); } + + case value_t::number_unsigned: + { + return static_cast(m_value.number_unsigned); + } case value_t::number_float: { @@ -2406,7 +2609,19 @@ class basic_json { return is_number_integer() ? &m_value.number_integer : nullptr; } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t*) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + /// get a pointer to the value (unsigned number) + const number_unsigned_t* get_impl_ptr(const number_unsigned_t*) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + /// get a pointer to the value (floating-point number) number_float_t* get_impl_ptr(number_float_t*) noexcept { @@ -2504,8 +2719,8 @@ class basic_json @warning The pointer becomes invalid if the underlying JSON object changes. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or @ref - number_float_t. + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise @@ -2555,8 +2770,8 @@ class basic_json state. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or @ref - number_float_t. + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise @@ -2674,14 +2889,14 @@ class basic_json @since version 1.0.0 */ - template < typename ValueType, typename - std::enable_if < - not std::is_pointer::value - and not std::is_same::value + template::value + and not std::is_same::value #ifndef _MSC_VER // Fix for issue #167 operator<< abiguity under VS2015 - and not std::is_same>::value + and not std::is_same>::value #endif - , int >::type = 0 > + , int>::type = 0> operator ValueType() const { // delegate the call to get<>() const @@ -3435,6 +3650,7 @@ class basic_json case value_t::boolean: case value_t::number_float: case value_t::number_integer: + case value_t::number_unsigned: case value_t::string: { if (not pos.m_it.primitive_iterator.is_begin()) @@ -3540,6 +3756,7 @@ class basic_json case value_t::boolean: case value_t::number_float: case value_t::number_integer: + case value_t::number_unsigned: case value_t::string: { if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) @@ -4229,6 +4446,12 @@ class basic_json break; } + case value_t::number_unsigned: + { + m_value.number_unsigned = 0; + break; + } + case value_t::number_float: { m_value.number_float = 0.0; @@ -4767,14 +4990,15 @@ class basic_json */ friend bool operator<(const value_t lhs, const value_t rhs) { - static constexpr std::array order = {{ + static constexpr std::array order = {{ 0, // null 3, // object 4, // array 5, // string 1, // boolean 2, // integer - 2 // float + 2, // unsigned + 2, // float } }; @@ -4850,6 +5074,10 @@ class basic_json { 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; @@ -4868,6 +5096,23 @@ class basic_json { return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(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(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(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(rhs.m_value.number_unsigned); + } + return false; } @@ -5019,6 +5264,10 @@ class basic_json { 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; @@ -5031,13 +5280,27 @@ class basic_json } else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) { - return static_cast(lhs.m_value.number_integer) < - rhs.m_value.number_float; + return static_cast(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(rhs.m_value.number_integer); + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(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(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(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; } // We only reach this line if we cannot compare values. In that case, @@ -5602,6 +5865,12 @@ class basic_json return; } + case value_t::number_unsigned: + { + o << m_value.number_unsigned; + return; + } + case value_t::number_float: { // If the number is an integer then output as a fixed with with @@ -6825,789 +7094,386 @@ class basic_json m_start = m_cursor; assert(m_start != nullptr); - - { - lexer_char_t yych; - unsigned int yyaccept = 0; - static const unsigned char yybm[] = - { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 32, 32, 0, 0, 32, 0, 0, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 96, 64, 0, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 0, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - }; - if ((m_limit - m_cursor) < 5) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= ':') - { - if (yych <= ' ') - { - if (yych <= '\n') - { - if (yych <= 0x00) - { - goto basic_json_parser_28; - } - if (yych <= 0x08) - { - goto basic_json_parser_30; - } - if (yych >= '\n') - { - goto basic_json_parser_4; - } - } - else - { - if (yych == '\r') - { - goto basic_json_parser_2; - } - if (yych <= 0x1F) - { - goto basic_json_parser_30; - } - } - } - else - { - if (yych <= ',') - { - if (yych == '"') - { - goto basic_json_parser_27; - } - if (yych <= '+') - { - goto basic_json_parser_30; - } - goto basic_json_parser_16; - } - else - { - if (yych <= '/') - { - if (yych <= '-') - { - goto basic_json_parser_23; - } - goto basic_json_parser_30; - } - else - { - if (yych <= '0') - { - goto basic_json_parser_24; - } - if (yych <= '9') - { - goto basic_json_parser_26; - } - goto basic_json_parser_18; - } - } + + { + lexer_char_t yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 32, 32, 0, 0, 32, 0, 0, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 96, 64, 0, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 0, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + }; + if ((m_limit - m_cursor) < 5) yyfill(); // LCOV_EXCL_LINE; + yych = *m_cursor; + if (yych <= ':') { + if (yych <= ' ') { + if (yych <= '\n') { + if (yych <= 0x00) goto basic_json_parser_28; + if (yych <= 0x08) goto basic_json_parser_30; + if (yych >= '\n') goto basic_json_parser_4; + } else { + if (yych == '\r') goto basic_json_parser_2; + if (yych <= 0x1F) goto basic_json_parser_30; + } + } else { + if (yych <= ',') { + if (yych == '"') goto basic_json_parser_27; + if (yych <= '+') goto basic_json_parser_30; + goto basic_json_parser_16; + } else { + if (yych <= '/') { + if (yych <= '-') goto basic_json_parser_23; + goto basic_json_parser_30; + } else { + if (yych <= '0') goto basic_json_parser_24; + if (yych <= '9') goto basic_json_parser_26; + goto basic_json_parser_18; } } - else - { - if (yych <= 'n') - { - if (yych <= ']') - { - if (yych == '[') - { - goto basic_json_parser_8; - } - if (yych <= '\\') - { - goto basic_json_parser_30; - } - goto basic_json_parser_10; - } - else - { - if (yych == 'f') - { - goto basic_json_parser_22; - } - if (yych <= 'm') - { - goto basic_json_parser_30; - } - goto basic_json_parser_20; - } - } - else - { - if (yych <= '{') - { - if (yych == 't') - { - goto basic_json_parser_21; - } - if (yych <= 'z') - { - goto basic_json_parser_30; - } - goto basic_json_parser_12; - } - else - { - if (yych <= '}') - { - if (yych <= '|') - { - goto basic_json_parser_30; - } - goto basic_json_parser_14; - } - else - { - if (yych == 0xEF) - { - goto basic_json_parser_6; - } - goto basic_json_parser_30; - } - } + } + } else { + if (yych <= 'n') { + if (yych <= ']') { + if (yych == '[') goto basic_json_parser_8; + if (yych <= '\\') goto basic_json_parser_30; + goto basic_json_parser_10; + } else { + if (yych == 'f') goto basic_json_parser_22; + if (yych <= 'm') goto basic_json_parser_30; + goto basic_json_parser_20; + } + } else { + if (yych <= '{') { + if (yych == 't') goto basic_json_parser_21; + if (yych <= 'z') goto basic_json_parser_30; + goto basic_json_parser_12; + } else { + if (yych <= '}') { + if (yych <= '|') goto basic_json_parser_30; + goto basic_json_parser_14; + } else { + if (yych == 0xEF) goto basic_json_parser_6; + goto basic_json_parser_30; } } + } + } basic_json_parser_2: - ++m_cursor; - yych = *m_cursor; - goto basic_json_parser_5; + ++m_cursor; + yych = *m_cursor; + goto basic_json_parser_5; basic_json_parser_3: - { - return scan(); - } + { return scan(); } basic_json_parser_4: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; + ++m_cursor; + if (m_limit <= m_cursor) yyfill(); // LCOV_EXCL_LINE; + yych = *m_cursor; basic_json_parser_5: - if (yybm[0 + yych] & 32) - { - goto basic_json_parser_4; - } - goto basic_json_parser_3; + if (yybm[0+yych] & 32) { + goto basic_json_parser_4; + } + goto basic_json_parser_3; basic_json_parser_6: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 0xBB) - { - goto basic_json_parser_64; - } + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 0xBB) goto basic_json_parser_64; basic_json_parser_7: - { - return token_type::parse_error; - } + { return token_type::parse_error; } basic_json_parser_8: - ++m_cursor; - { - return token_type::begin_array; - } + ++m_cursor; + { return token_type::begin_array; } basic_json_parser_10: - ++m_cursor; - { - return token_type::end_array; - } + ++m_cursor; + { return token_type::end_array; } basic_json_parser_12: - ++m_cursor; - { - return token_type::begin_object; - } + ++m_cursor; + { return token_type::begin_object; } basic_json_parser_14: - ++m_cursor; - { - return token_type::end_object; - } + ++m_cursor; + { return token_type::end_object; } basic_json_parser_16: - ++m_cursor; - { - return token_type::value_separator; - } + ++m_cursor; + { return token_type::value_separator; } basic_json_parser_18: - ++m_cursor; - { - return token_type::name_separator; - } + ++m_cursor; + { return token_type::name_separator; } basic_json_parser_20: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 'u') - { - goto basic_json_parser_60; - } - goto basic_json_parser_7; + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'u') goto basic_json_parser_60; + goto basic_json_parser_7; basic_json_parser_21: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 'r') - { - goto basic_json_parser_56; - } - goto basic_json_parser_7; + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'r') goto basic_json_parser_56; + goto basic_json_parser_7; basic_json_parser_22: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 'a') - { - goto basic_json_parser_51; - } - goto basic_json_parser_7; + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'a') goto basic_json_parser_51; + goto basic_json_parser_7; basic_json_parser_23: - yych = *++m_cursor; - if (yych <= '/') - { - goto basic_json_parser_7; - } - if (yych <= '0') - { - goto basic_json_parser_50; - } - if (yych <= '9') - { - goto basic_json_parser_41; - } - goto basic_json_parser_7; + yych = *++m_cursor; + if (yych <= '/') goto basic_json_parser_7; + if (yych <= '0') goto basic_json_parser_50; + if (yych <= '9') goto basic_json_parser_41; + goto basic_json_parser_7; basic_json_parser_24: - yyaccept = 1; - yych = *(m_marker = ++m_cursor); - if (yych <= 'D') - { - if (yych == '.') - { - goto basic_json_parser_43; - } - } - else - { - if (yych <= 'E') - { - goto basic_json_parser_44; - } - if (yych == 'e') - { - goto basic_json_parser_44; - } - } + yyaccept = 1; + yych = *(m_marker = ++m_cursor); + if (yych <= 'D') { + if (yych == '.') goto basic_json_parser_43; + } else { + if (yych <= 'E') goto basic_json_parser_44; + if (yych == 'e') goto basic_json_parser_44; + } basic_json_parser_25: - { - return token_type::value_number; - } + { return token_type::value_number; } basic_json_parser_26: - yyaccept = 1; - yych = *(m_marker = ++m_cursor); - goto basic_json_parser_42; + yyaccept = 1; + yych = *(m_marker = ++m_cursor); + goto basic_json_parser_42; basic_json_parser_27: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych <= 0x0F) - { - goto basic_json_parser_7; - } - goto basic_json_parser_32; + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych <= 0x0F) goto basic_json_parser_7; + goto basic_json_parser_32; basic_json_parser_28: - ++m_cursor; - { - return token_type::end_of_input; - } + ++m_cursor; + { return token_type::end_of_input; } basic_json_parser_30: - yych = *++m_cursor; - goto basic_json_parser_7; + yych = *++m_cursor; + goto basic_json_parser_7; basic_json_parser_31: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; + ++m_cursor; + if (m_limit <= m_cursor) yyfill(); // LCOV_EXCL_LINE; + yych = *m_cursor; basic_json_parser_32: - if (yybm[0 + yych] & 64) - { - goto basic_json_parser_31; - } - if (yych <= 0x0F) - { - goto basic_json_parser_33; - } - if (yych <= '"') - { - goto basic_json_parser_35; - } - goto basic_json_parser_34; + if (yybm[0+yych] & 64) { + goto basic_json_parser_31; + } + if (yych <= 0x0F) goto basic_json_parser_33; + if (yych <= '"') goto basic_json_parser_35; + goto basic_json_parser_34; basic_json_parser_33: - m_cursor = m_marker; - if (yyaccept == 0) - { - goto basic_json_parser_7; - } - else - { - goto basic_json_parser_25; - } + m_cursor = m_marker; + if (yyaccept == 0) { + goto basic_json_parser_7; + } else { + goto basic_json_parser_25; + } basic_json_parser_34: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= 'e') - { - if (yych <= '/') - { - if (yych == '"') - { - goto basic_json_parser_31; - } - if (yych <= '.') - { - goto basic_json_parser_33; - } - goto basic_json_parser_31; - } - else - { - if (yych <= '\\') - { - if (yych <= '[') - { - goto basic_json_parser_33; - } - goto basic_json_parser_31; - } - else - { - if (yych == 'b') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; - } - } + ++m_cursor; + if (m_limit <= m_cursor) yyfill(); // LCOV_EXCL_LINE; + yych = *m_cursor; + if (yych <= 'e') { + if (yych <= '/') { + if (yych == '"') goto basic_json_parser_31; + if (yych <= '.') goto basic_json_parser_33; + goto basic_json_parser_31; + } else { + if (yych <= '\\') { + if (yych <= '[') goto basic_json_parser_33; + goto basic_json_parser_31; + } else { + if (yych == 'b') goto basic_json_parser_31; + goto basic_json_parser_33; } - else - { - if (yych <= 'q') - { - if (yych <= 'f') - { - goto basic_json_parser_31; - } - if (yych == 'n') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; - } - else - { - if (yych <= 's') - { - if (yych <= 'r') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; - } - else - { - if (yych <= 't') - { - goto basic_json_parser_31; - } - if (yych <= 'u') - { - goto basic_json_parser_37; - } - goto basic_json_parser_33; - } - } + } + } else { + if (yych <= 'q') { + if (yych <= 'f') goto basic_json_parser_31; + if (yych == 'n') goto basic_json_parser_31; + goto basic_json_parser_33; + } else { + if (yych <= 's') { + if (yych <= 'r') goto basic_json_parser_31; + goto basic_json_parser_33; + } else { + if (yych <= 't') goto basic_json_parser_31; + if (yych <= 'u') goto basic_json_parser_37; + goto basic_json_parser_33; } + } + } basic_json_parser_35: - ++m_cursor; - { - return token_type::value_string; - } + ++m_cursor; + { return token_type::value_string; } basic_json_parser_37: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= '@') - { - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych >= ':') - { - goto basic_json_parser_33; - } - } - else - { - if (yych <= 'F') - { - goto basic_json_parser_38; - } - if (yych <= '`') - { - goto basic_json_parser_33; - } - if (yych >= 'g') - { - goto basic_json_parser_33; - } - } + ++m_cursor; + if (m_limit <= m_cursor) yyfill(); // LCOV_EXCL_LINE; + yych = *m_cursor; + if (yych <= '@') { + if (yych <= '/') goto basic_json_parser_33; + if (yych >= ':') goto basic_json_parser_33; + } else { + if (yych <= 'F') goto basic_json_parser_38; + if (yych <= '`') goto basic_json_parser_33; + if (yych >= 'g') goto basic_json_parser_33; + } basic_json_parser_38: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= '@') - { - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych >= ':') - { - goto basic_json_parser_33; - } - } - else - { - if (yych <= 'F') - { - goto basic_json_parser_39; - } - if (yych <= '`') - { - goto basic_json_parser_33; - } - if (yych >= 'g') - { - goto basic_json_parser_33; - } - } + ++m_cursor; + if (m_limit <= m_cursor) yyfill(); // LCOV_EXCL_LINE; + yych = *m_cursor; + if (yych <= '@') { + if (yych <= '/') goto basic_json_parser_33; + if (yych >= ':') goto basic_json_parser_33; + } else { + if (yych <= 'F') goto basic_json_parser_39; + if (yych <= '`') goto basic_json_parser_33; + if (yych >= 'g') goto basic_json_parser_33; + } basic_json_parser_39: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= '@') - { - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych >= ':') - { - goto basic_json_parser_33; - } - } - else - { - if (yych <= 'F') - { - goto basic_json_parser_40; - } - if (yych <= '`') - { - goto basic_json_parser_33; - } - if (yych >= 'g') - { - goto basic_json_parser_33; - } - } + ++m_cursor; + if (m_limit <= m_cursor) yyfill(); // LCOV_EXCL_LINE; + yych = *m_cursor; + if (yych <= '@') { + if (yych <= '/') goto basic_json_parser_33; + if (yych >= ':') goto basic_json_parser_33; + } else { + if (yych <= 'F') goto basic_json_parser_40; + if (yych <= '`') goto basic_json_parser_33; + if (yych >= 'g') goto basic_json_parser_33; + } basic_json_parser_40: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= '@') - { - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych <= '9') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; - } - else - { - if (yych <= 'F') - { - goto basic_json_parser_31; - } - if (yych <= '`') - { - goto basic_json_parser_33; - } - if (yych <= 'f') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; - } + ++m_cursor; + if (m_limit <= m_cursor) yyfill(); // LCOV_EXCL_LINE; + yych = *m_cursor; + if (yych <= '@') { + if (yych <= '/') goto basic_json_parser_33; + if (yych <= '9') goto basic_json_parser_31; + goto basic_json_parser_33; + } else { + if (yych <= 'F') goto basic_json_parser_31; + if (yych <= '`') goto basic_json_parser_33; + if (yych <= 'f') goto basic_json_parser_31; + goto basic_json_parser_33; + } basic_json_parser_41: - yyaccept = 1; - m_marker = ++m_cursor; - if ((m_limit - m_cursor) < 3) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; + yyaccept = 1; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) yyfill(); // LCOV_EXCL_LINE; + yych = *m_cursor; basic_json_parser_42: - if (yybm[0 + yych] & 128) - { - goto basic_json_parser_41; - } - if (yych <= 'D') - { - if (yych != '.') - { - goto basic_json_parser_25; - } - } - else - { - if (yych <= 'E') - { - goto basic_json_parser_44; - } - if (yych == 'e') - { - goto basic_json_parser_44; - } - goto basic_json_parser_25; - } + if (yybm[0+yych] & 128) { + goto basic_json_parser_41; + } + if (yych <= 'D') { + if (yych != '.') goto basic_json_parser_25; + } else { + if (yych <= 'E') goto basic_json_parser_44; + if (yych == 'e') goto basic_json_parser_44; + goto basic_json_parser_25; + } basic_json_parser_43: - yych = *++m_cursor; - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych <= '9') - { - goto basic_json_parser_48; - } - goto basic_json_parser_33; + yych = *++m_cursor; + if (yych <= '/') goto basic_json_parser_33; + if (yych <= '9') goto basic_json_parser_48; + goto basic_json_parser_33; basic_json_parser_44: - yych = *++m_cursor; - if (yych <= ',') - { - if (yych != '+') - { - goto basic_json_parser_33; - } - } - else - { - if (yych <= '-') - { - goto basic_json_parser_45; - } - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych <= '9') - { - goto basic_json_parser_46; - } - goto basic_json_parser_33; - } + yych = *++m_cursor; + if (yych <= ',') { + if (yych != '+') goto basic_json_parser_33; + } else { + if (yych <= '-') goto basic_json_parser_45; + if (yych <= '/') goto basic_json_parser_33; + if (yych <= '9') goto basic_json_parser_46; + goto basic_json_parser_33; + } basic_json_parser_45: - yych = *++m_cursor; - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych >= ':') - { - goto basic_json_parser_33; - } + yych = *++m_cursor; + if (yych <= '/') goto basic_json_parser_33; + if (yych >= ':') goto basic_json_parser_33; basic_json_parser_46: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= '/') - { - goto basic_json_parser_25; - } - if (yych <= '9') - { - goto basic_json_parser_46; - } - goto basic_json_parser_25; + ++m_cursor; + if (m_limit <= m_cursor) yyfill(); // LCOV_EXCL_LINE; + yych = *m_cursor; + if (yych <= '/') goto basic_json_parser_25; + if (yych <= '9') goto basic_json_parser_46; + goto basic_json_parser_25; basic_json_parser_48: - yyaccept = 1; - m_marker = ++m_cursor; - if ((m_limit - m_cursor) < 3) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= 'D') - { - if (yych <= '/') - { - goto basic_json_parser_25; - } - if (yych <= '9') - { - goto basic_json_parser_48; - } - goto basic_json_parser_25; - } - else - { - if (yych <= 'E') - { - goto basic_json_parser_44; - } - if (yych == 'e') - { - goto basic_json_parser_44; - } - goto basic_json_parser_25; - } + yyaccept = 1; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) yyfill(); // LCOV_EXCL_LINE; + yych = *m_cursor; + if (yych <= 'D') { + if (yych <= '/') goto basic_json_parser_25; + if (yych <= '9') goto basic_json_parser_48; + goto basic_json_parser_25; + } else { + if (yych <= 'E') goto basic_json_parser_44; + if (yych == 'e') goto basic_json_parser_44; + goto basic_json_parser_25; + } basic_json_parser_50: - yyaccept = 1; - yych = *(m_marker = ++m_cursor); - if (yych <= 'D') - { - if (yych == '.') - { - goto basic_json_parser_43; - } - goto basic_json_parser_25; - } - else - { - if (yych <= 'E') - { - goto basic_json_parser_44; - } - if (yych == 'e') - { - goto basic_json_parser_44; - } - goto basic_json_parser_25; - } + yyaccept = 1; + yych = *(m_marker = ++m_cursor); + if (yych <= 'D') { + if (yych == '.') goto basic_json_parser_43; + goto basic_json_parser_25; + } else { + if (yych <= 'E') goto basic_json_parser_44; + if (yych == 'e') goto basic_json_parser_44; + goto basic_json_parser_25; + } basic_json_parser_51: - yych = *++m_cursor; - if (yych != 'l') - { - goto basic_json_parser_33; - } - yych = *++m_cursor; - if (yych != 's') - { - goto basic_json_parser_33; - } - yych = *++m_cursor; - if (yych != 'e') - { - goto basic_json_parser_33; - } - ++m_cursor; - { - return token_type::literal_false; - } + yych = *++m_cursor; + if (yych != 'l') goto basic_json_parser_33; + yych = *++m_cursor; + if (yych != 's') goto basic_json_parser_33; + yych = *++m_cursor; + if (yych != 'e') goto basic_json_parser_33; + ++m_cursor; + { return token_type::literal_false; } basic_json_parser_56: - yych = *++m_cursor; - if (yych != 'u') - { - goto basic_json_parser_33; - } - yych = *++m_cursor; - if (yych != 'e') - { - goto basic_json_parser_33; - } - ++m_cursor; - { - return token_type::literal_true; - } + yych = *++m_cursor; + if (yych != 'u') goto basic_json_parser_33; + yych = *++m_cursor; + if (yych != 'e') goto basic_json_parser_33; + ++m_cursor; + { return token_type::literal_true; } basic_json_parser_60: - yych = *++m_cursor; - if (yych != 'l') - { - goto basic_json_parser_33; - } - yych = *++m_cursor; - if (yych != 'l') - { - goto basic_json_parser_33; - } - ++m_cursor; - { - return token_type::literal_null; - } + yych = *++m_cursor; + if (yych != 'l') goto basic_json_parser_33; + yych = *++m_cursor; + if (yych != 'l') goto basic_json_parser_33; + ++m_cursor; + { return token_type::literal_null; } basic_json_parser_64: - yych = *++m_cursor; - if (yych != 0xBF) - { - goto basic_json_parser_33; - } - ++m_cursor; - { - return scan(); - } - } + yych = *++m_cursor; + if (yych != 0xBF) goto basic_json_parser_33; + ++m_cursor; + { return scan(); } + } } @@ -7774,9 +7640,9 @@ class basic_json @brief parse floating point number This function (and its overloads) serves to select the most approprate - standard floating point number parsing function (i.e., `std::strtof`, - `std::strtod`, or `std::strtold`) based on the type supplied via the - first parameter. Set this to @a static_cast(nullptr). + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to + @a static_cast(nullptr). @param[in] type the @ref number_float_t in use @@ -7795,45 +7661,135 @@ class basic_json return std::strtold(reinterpret_cast(m_start), endptr); } - /// @copydoc str_to_float_t - double str_to_float_t(double*, char** endptr) const + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to + @a static_cast(nullptr). + + @param type the @ref number_float_t in use + + @param endptr recieves a pointer to the first character after the number + + @return the floating point number + */ + double str_to_float_t(double* /* type */, char** endptr) const { return std::strtod(reinterpret_cast(m_start), endptr); } - /// @copydoc str_to_float_t - float str_to_float_t(float*, char** endptr) const + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to + @a static_cast(nullptr). + + @param type the @ref number_float_t in use + + @param endptr recieves a pointer to the first character after the number + + @return the floating point number + */ + float str_to_float_t(float* /* type */, char** endptr) const { return std::strtof(reinterpret_cast(m_start), endptr); } /*! - @brief return number value for number tokens + @brief static_cast between two types and indicate if it results in error + + This function performs a static_cast between @a source and @a dest. It + then checks if a static_cast back to @a dest produces an error. + + @param[in] source the value to cast from + + @param[out] dest the value to cast to - This function translates the last token into a floating point number. - The pointer m_start points to the beginning of the parsed number. We - pass this pointer to std::strtod which sets endptr to the first - character past the converted number. If this pointer is not the same as - m_cursor, then either more or less characters have been used during the - comparison. This can happen for inputs like "01" which will be treated - like number 0 followed by number 1. + @return @a true if the cast was performed without error, @a false otherwise + */ + template + bool attempt_cast(T_A source, T_B & dest) const + { + dest = static_cast(source); + return (source == static_cast(dest)); + } - @return the result of the number conversion or NAN if the conversion - read past the current token. The latter case needs to be treated by the - caller function. + /*! + @brief return number value for number tokens - @throw std::range_error if passed value is out of range + This function translates the last token into the most appropriate + number type (either integer, unsigned integer or floating point), + which is passed back to the caller via the result parameter. The pointer + @a m_start points to the beginning of the parsed number. We first examine + the first character to determine the sign of the number and then pass + this pointer to either @a std::strtoull (if positive) or @a std::strtoll + (if negative), both of which set @a endptr to the first character past the + converted number. If this pointer is not the same as @a m_cursor, then + either more or less characters have been used during the comparison. + + This can happen for inputs like "01" which will be treated like number 0 + followed by number 1. This will also occur for valid floating point + inputs like "12e3" will be incorrectly read as 12. Numbers that are too + large or too small for a signed/unsigned long long will cause a range + error (@a errno set to ERANGE). The parsed number is cast to a @ref + number_integer_t/@ref number_unsigned_t using the helper function @ref attempt_cast, + which returns @a false if the cast could not be peformed without error. + + In any of these cases (more/less characters read, range error or a cast + error) the pointer is passed to @a std:strtod, which also sets @a endptr to the + first character past the converted number. The resulting @ref number_float_t + is then cast to a @ref number_integer_t/@ref number_unsigned_t using + @ref attempt_cast and if no error occurs is stored in that form, otherwise + it is stored as a @ref number_float_t. + + A final comparison is made of @a endptr and if still not the same as + @ref m_cursor a bad input is assumed and @a result parameter is set to NAN. + + @param[out] result @ref basic_json object to receive the number, or NAN if the + conversion read past the current token. The latter case needs to be + treated by the caller function. */ - number_float_t get_number() const + void get_number(basic_json& result) const { - // conversion typename string_t::value_type* endptr; assert(m_start != nullptr); - number_float_t float_val = str_to_float_t(static_cast(nullptr), &endptr); + errno = 0; + + // Attempt to parse it as an integer - first checking for a negative number + if (*reinterpret_cast(m_start) != '-') + { + // Positive, parse with strtoull and attempt cast to number_unsigned_t + if (attempt_cast(std::strtoull(reinterpret_cast(m_start), &endptr, 10), result.m_value.number_unsigned)) + result.m_type = value_t::number_unsigned; + else result.m_type = value_t::number_float; // Cast failed due to overflow - store as float + } + else + { + // Negative, parse with strtoll and attempt cast to number_integer_t + if (attempt_cast(std::strtoll(reinterpret_cast(m_start), &endptr, 10), result.m_value.number_unsigned)) + result.m_type = value_t::number_integer; + else result.m_type = value_t::number_float; // Cast failed due to overflow - store as float + } - // return float_val if the whole number was translated and NAN - // otherwise - return (reinterpret_cast(endptr) == m_cursor) ? float_val : NAN; + // Check the end of the number was reached and no range error occurred + if (reinterpret_cast(endptr) != m_cursor || errno == ERANGE) result.m_type = value_t::number_float; + + if (result.m_type == value_t::number_float) + { + // Either the number won't fit in an integer (range error from strtoull/strtoll or overflow on cast) or there was + // something else after the number, which could be an exponent + + // Parse with strtod + result.m_value.number_float = str_to_float_t(static_cast(nullptr), &endptr); + + // Anything after the number is an error + if(reinterpret_cast(endptr) != m_cursor) + throw std::invalid_argument(std::string("parse error - ") + get_token() + " is not a number"); + } } private: @@ -8063,32 +8019,8 @@ class basic_json case lexer::token_type::value_number: { - result.m_value = m_lexer.get_number(); - - // NAN is returned if token could not be translated - // completely - if (std::isnan(result.m_value.number_float)) - { - throw std::invalid_argument(std::string("parse error - ") + - m_lexer.get_token() + " is not a number"); - } - + m_lexer.get_number(result); get_token(); - - // check if conversion loses precision (special case -0.0 always loses precision) - const auto int_val = static_cast(result.m_value.number_float); - if (result.m_value.number_float == static_cast(int_val) and - result.m_value.number_integer != json_value(-0.0f).number_integer) - { - // we would not lose precision -> return int - result.m_type = value_t::number_integer; - result.m_value = int_val; - } - else - { - // we would lose precision -> return float - result.m_type = value_t::number_float; - } break; } @@ -8228,3 +8160,5 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t) #endif #endif + + diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index c8f1ca678d..4939f19474 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -124,6 +124,8 @@ default; will be used in @ref string_t) in @ref boolean_t) @tparam NumberIntegerType type for JSON integer numbers (@c `int64_t` by default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c `uint64_t` by +default; will be used in @ref number_unsigned_t) @tparam NumberFloatType type for JSON floating-point numbers (@c `double` by default; will be used in @ref number_float_t) @tparam AllocatorType type of the allocator to use (@c `std::allocator` by @@ -185,6 +187,7 @@ template < class StringType = std::string, class BooleanType = bool, class NumberIntegerType = int64_t, + class NumberUnsignedType = uint64_t, class NumberFloatType = double, template class AllocatorType = std::allocator > @@ -197,6 +200,7 @@ class basic_json StringType, BooleanType, NumberIntegerType, + NumberUnsignedType, NumberFloatType, AllocatorType>; @@ -475,9 +479,10 @@ class basic_json > permitted. This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is an - integer or a floating-point number. Therefore, two different types, @ref - number_integer_t and @ref number_float_t are used. + C++ allows more precise storage if it is known whether the number is a + signed integer, an unsigned integer or a floating-point number. Therefore, + three different types, @ref number_integer_t, @ref number_unsigned_t and + @ref number_float_t are used. To store integer numbers in C++, a type is defined by the template parameter @a NumberIntegerType which chooses the type to use. @@ -510,7 +515,7 @@ class basic_json that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers that are out of range will yield over/underflow when used in a constructor. During deserialization, too large or small integer numbers will be - automatically be stored as @ref number_float_t. + automatically be stored as @ref number_unsigned_t or @ref number_float_t. [RFC 7159](http://rfc7159.net/rfc7159) further states: > Note that when such software is used, numbers that are integers and are @@ -526,10 +531,84 @@ class basic_json @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + @since version 1.0.0 */ using number_integer_t = NumberIntegerType; + /*! + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most programming + > languages. A number is represented in base 10 using decimal digits. It + > contains an integer component that may be prefixed with an optional minus + > sign, which may be followed by a fraction part and/or an exponent part. + > Leading zeros are not allowed. (...) Numeric values that cannot be + > represented in the grammar below (such as Infinity and NaN) are not + > permitted. + + This description includes both integer and floating-point numbers. However, + C++ allows more precise storage if it is known whether the number is a + signed integer, an unsigned integer or a floating-point number. Therefore, + three different types, @ref number_integer_t, @ref number_unsigned_t and + @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the template + parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the default + value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer number + that can be stored is `0`. Integer numbers + that are out of range will yield over/underflow when used in a constructor. + During deserialization, too large or small integer numbers will be + automatically be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], this + class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @sa @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + /*! @brief a type for a number (floating-point) @@ -543,9 +622,10 @@ class basic_json > permitted. This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is an - integer or a floating-point number. Therefore, two different types, @ref - number_integer_t and @ref number_float_t are used. + C++ allows more precise storage if it is known whether the number is a + signed integer, an unsigned integer or a floating-point number. Therefore, + three different types, @ref number_integer_t, @ref number_unsigned_t and + @ref number_float_t are used. To store floating-point numbers in C++, a type is defined by the template parameter @a NumberFloatType which chooses the type to use. @@ -591,6 +671,8 @@ class basic_json @sa @ref number_integer_t -- type for number values (integer) + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + @since version 1.0.0 */ using number_float_t = NumberFloatType; @@ -620,6 +702,7 @@ class basic_json string, ///< string value boolean, ///< boolean value number_integer, ///< number value (integer) + number_unsigned,///< number value (unsigned integer) number_float, ///< number value (floating-point) discarded ///< discarded by the the parser callback function }; @@ -663,6 +746,8 @@ class basic_json boolean_t boolean; /// number (integer) number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; /// number (floating-point) number_float_t number_float; @@ -672,6 +757,8 @@ class basic_json json_value(boolean_t v) noexcept : boolean(v) {} /// constructor for numbers (integer) json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} /// constructor for numbers (floating-point) json_value(number_float_t v) noexcept : number_float(v) {} /// constructor for empty values of a given type @@ -708,6 +795,12 @@ class basic_json number_integer = number_integer_t(0); break; } + + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } case value_t::number_float: { @@ -864,6 +957,8 @@ class basic_json (floating-point) value @sa @ref basic_json(const number_integer_t) -- create a number (integer) value + @sa @ref basic_json(const number_unsigned_t) -- create a number (unsigned) + value @since version 1.0.0 */ @@ -1165,7 +1260,8 @@ class basic_json typename std::enable_if< not (std::is_same::value) and std::is_same::value - , int>::type = 0> + , int>::type + = 0> basic_json(const number_integer_t val) : m_type(value_t::number_integer), m_value(val) {} @@ -1228,13 +1324,74 @@ class basic_json template::value and - std::numeric_limits::is_integer, CompatibleNumberIntegerType>::type + std::numeric_limits::is_integer and + std::numeric_limits::is_signed, + CompatibleNumberIntegerType>::type = 0> basic_json(const CompatibleNumberIntegerType val) noexcept : m_type(value_t::number_integer), m_value(static_cast(val)) {} + /*! + @brief create an unsigned integer number (explicit) + + Create an unsigned integer number JSON value with a given content. + + @tparam T helper type to compare number_unsigned_t and unsigned int + (not visible in) the interface. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @sa @ref basic_json(const CompatibleNumberUnsignedType) -- create a number + value (unsigned integer) from a compatible number type + + @since version 2.0.0 + */ + template::value) + and std::is_same::value + , int>::type + = 0> + basic_json(const number_unsigned_t val) + : m_type(value_t::number_unsigned), m_value(val) + {} + + /*! + @brief create an unsigned number (implicit) + + Create an unsigned number JSON value with a given content. This constructor + allows any type that can be used to construct values of type @ref + number_unsigned_t. Examples may include the types `unsigned int`, `uint32_t`, + or `unsigned short`. + + @tparam CompatibleNumberUnsignedType an integer type which is compatible to + @ref number_unsigned_t. + + @param[in] val an unsigned integer to create a JSON number from + + @complexity Constant. + + @sa @ref basic_json(const number_unsigned_t) -- create a number value + (unsigned) + + @since version 2.0.0 + */ + template::value and + std::numeric_limits::is_integer and + !std::numeric_limits::is_signed, + CompatibleNumberUnsignedType>::type + = 0> + basic_json(const CompatibleNumberUnsignedType val) noexcept + : m_type(value_t::number_unsigned), + m_value(static_cast(val)) + {} + /*! @brief create a floating-point number (explicit) @@ -1594,6 +1751,7 @@ class basic_json case value_t::boolean: case value_t::number_float: case value_t::number_integer: + case value_t::number_unsigned: case value_t::string: { if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) @@ -1617,6 +1775,13 @@ class basic_json m_value.number_integer = first.m_object->m_value.number_integer; break; } + + case value_t::number_unsigned: + { + assert(first.m_object != nullptr); + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } case value_t::number_float: { @@ -1720,6 +1885,12 @@ class basic_json m_value = other.m_value.number_integer; break; } + + case value_t::number_unsigned: + { + m_value = other.m_value.number_unsigned; + break; + } case value_t::number_float: { @@ -1998,15 +2169,17 @@ class basic_json This function returns true iff the JSON value is a number. This includes both integer and floating-point values. - @return `true` if type is number (regardless whether integer or - floating-type), `false` otherwise. + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. @complexity Constant. @liveexample{The following code exemplifies @ref is_number for all JSON types.,is_number} - @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer number @sa @ref is_number_float() -- check if value is a floating-point number @since version 1.0.0 @@ -2019,10 +2192,11 @@ class basic_json /*! @brief return whether value is an integer number - This function returns true iff the JSON value is an integer number. This - excludes floating-point values. + This function returns true iff the JSON value is an integer or unsigned + integer number. This excludes floating-point values. - @return `true` if type is an integer number, `false` otherwise. + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. @complexity Constant. @@ -2030,20 +2204,43 @@ class basic_json JSON types.,is_number_integer} @sa @ref is_number() -- check if value is a number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer number @sa @ref is_number_float() -- check if value is a floating-point number @since version 1.0.0 */ bool is_number_integer() const noexcept { - return m_type == value_t::number_integer; + return m_type == value_t::number_integer or m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true iff the JSON value is an unsigned integer number. + This excludes floating-point and (signed) integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + bool is_number_unsigned() const noexcept + { + return m_type == value_t::number_unsigned; } /*! @brief return whether value is a floating-point number This function returns true iff the JSON value is a floating-point number. - This excludes integer values. + This excludes integer and unsigned integer values. @return `true` if type is a floating-point number, `false` otherwise. @@ -2054,6 +2251,7 @@ class basic_json @sa @ref is_number() -- check if value is number @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer number @since version 1.0.0 */ @@ -2321,6 +2519,11 @@ class basic_json { return static_cast(m_value.number_integer); } + + case value_t::number_unsigned: + { + return static_cast(m_value.number_unsigned); + } case value_t::number_float: { @@ -2406,7 +2609,19 @@ class basic_json { return is_number_integer() ? &m_value.number_integer : nullptr; } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t*) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + /// get a pointer to the value (unsigned number) + const number_unsigned_t* get_impl_ptr(const number_unsigned_t*) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + /// get a pointer to the value (floating-point number) number_float_t* get_impl_ptr(number_float_t*) noexcept { @@ -2504,8 +2719,8 @@ class basic_json @warning The pointer becomes invalid if the underlying JSON object changes. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or @ref - number_float_t. + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise @@ -2555,8 +2770,8 @@ class basic_json state. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or @ref - number_float_t. + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise @@ -2674,14 +2889,14 @@ class basic_json @since version 1.0.0 */ - template < typename ValueType, typename - std::enable_if < - not std::is_pointer::value - and not std::is_same::value + template::value + and not std::is_same::value #ifndef _MSC_VER // Fix for issue #167 operator<< abiguity under VS2015 - and not std::is_same>::value + and not std::is_same>::value #endif - , int >::type = 0 > + , int>::type = 0> operator ValueType() const { // delegate the call to get<>() const @@ -3435,6 +3650,7 @@ class basic_json case value_t::boolean: case value_t::number_float: case value_t::number_integer: + case value_t::number_unsigned: case value_t::string: { if (not pos.m_it.primitive_iterator.is_begin()) @@ -3540,6 +3756,7 @@ class basic_json case value_t::boolean: case value_t::number_float: case value_t::number_integer: + case value_t::number_unsigned: case value_t::string: { if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) @@ -4229,6 +4446,12 @@ class basic_json break; } + case value_t::number_unsigned: + { + m_value.number_unsigned = 0; + break; + } + case value_t::number_float: { m_value.number_float = 0.0; @@ -4767,14 +4990,15 @@ class basic_json */ friend bool operator<(const value_t lhs, const value_t rhs) { - static constexpr std::array order = {{ + static constexpr std::array order = {{ 0, // null 3, // object 4, // array 5, // string 1, // boolean 2, // integer - 2 // float + 2, // unsigned + 2, // float } }; @@ -4850,6 +5074,10 @@ class basic_json { 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; @@ -4868,6 +5096,23 @@ class basic_json { return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(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(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(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(rhs.m_value.number_unsigned); + } + return false; } @@ -5019,6 +5264,10 @@ class basic_json { 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; @@ -5031,13 +5280,27 @@ class basic_json } else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) { - return static_cast(lhs.m_value.number_integer) < - rhs.m_value.number_float; + return static_cast(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(rhs.m_value.number_integer); + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(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(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(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; } // We only reach this line if we cannot compare values. In that case, @@ -5602,6 +5865,12 @@ class basic_json return; } + case value_t::number_unsigned: + { + o << m_value.number_unsigned; + return; + } + case value_t::number_float: { // If the number is an integer then output as a fixed with with @@ -7053,9 +7322,9 @@ class basic_json @brief parse floating point number This function (and its overloads) serves to select the most approprate - standard floating point number parsing function (i.e., `std::strtof`, - `std::strtod`, or `std::strtold`) based on the type supplied via the - first parameter. Set this to @a static_cast(nullptr). + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to + @a static_cast(nullptr). @param[in] type the @ref number_float_t in use @@ -7074,45 +7343,135 @@ class basic_json return std::strtold(reinterpret_cast(m_start), endptr); } - /// @copydoc str_to_float_t - double str_to_float_t(double*, char** endptr) const + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to + @a static_cast(nullptr). + + @param type the @ref number_float_t in use + + @param endptr recieves a pointer to the first character after the number + + @return the floating point number + */ + double str_to_float_t(double* /* type */, char** endptr) const { return std::strtod(reinterpret_cast(m_start), endptr); } - /// @copydoc str_to_float_t - float str_to_float_t(float*, char** endptr) const + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to + @a static_cast(nullptr). + + @param type the @ref number_float_t in use + + @param endptr recieves a pointer to the first character after the number + + @return the floating point number + */ + float str_to_float_t(float* /* type */, char** endptr) const { return std::strtof(reinterpret_cast(m_start), endptr); } /*! - @brief return number value for number tokens + @brief static_cast between two types and indicate if it results in error + + This function performs a static_cast between @a source and @a dest. It + then checks if a static_cast back to @a dest produces an error. - This function translates the last token into a floating point number. - The pointer m_start points to the beginning of the parsed number. We - pass this pointer to std::strtod which sets endptr to the first - character past the converted number. If this pointer is not the same as - m_cursor, then either more or less characters have been used during the - comparison. This can happen for inputs like "01" which will be treated - like number 0 followed by number 1. + @param[in] source the value to cast from - @return the result of the number conversion or NAN if the conversion - read past the current token. The latter case needs to be treated by the - caller function. + @param[out] dest the value to cast to - @throw std::range_error if passed value is out of range + @return @a true if the cast was performed without error, @a false otherwise */ - number_float_t get_number() const + template + bool attempt_cast(T_A source, T_B & dest) const + { + dest = static_cast(source); + return (source == static_cast(dest)); + } + + /*! + @brief return number value for number tokens + + This function translates the last token into the most appropriate + number type (either integer, unsigned integer or floating point), + which is passed back to the caller via the result parameter. The pointer + @a m_start points to the beginning of the parsed number. We first examine + the first character to determine the sign of the number and then pass + this pointer to either @a std::strtoull (if positive) or @a std::strtoll + (if negative), both of which set @a endptr to the first character past the + converted number. If this pointer is not the same as @a m_cursor, then + either more or less characters have been used during the comparison. + + This can happen for inputs like "01" which will be treated like number 0 + followed by number 1. This will also occur for valid floating point + inputs like "12e3" will be incorrectly read as 12. Numbers that are too + large or too small for a signed/unsigned long long will cause a range + error (@a errno set to ERANGE). The parsed number is cast to a @ref + number_integer_t/@ref number_unsigned_t using the helper function @ref attempt_cast, + which returns @a false if the cast could not be peformed without error. + + In any of these cases (more/less characters read, range error or a cast + error) the pointer is passed to @a std:strtod, which also sets @a endptr to the + first character past the converted number. The resulting @ref number_float_t + is then cast to a @ref number_integer_t/@ref number_unsigned_t using + @ref attempt_cast and if no error occurs is stored in that form, otherwise + it is stored as a @ref number_float_t. + + A final comparison is made of @a endptr and if still not the same as + @ref m_cursor a bad input is assumed and @a result parameter is set to NAN. + + @param[out] result @ref basic_json object to receive the number, or NAN if the + conversion read past the current token. The latter case needs to be + treated by the caller function. + */ + void get_number(basic_json& result) const { - // conversion typename string_t::value_type* endptr; assert(m_start != nullptr); - number_float_t float_val = str_to_float_t(static_cast(nullptr), &endptr); + errno = 0; + + // Attempt to parse it as an integer - first checking for a negative number + if (*reinterpret_cast(m_start) != '-') + { + // Positive, parse with strtoull and attempt cast to number_unsigned_t + if (attempt_cast(std::strtoull(reinterpret_cast(m_start), &endptr, 10), result.m_value.number_unsigned)) + result.m_type = value_t::number_unsigned; + else result.m_type = value_t::number_float; // Cast failed due to overflow - store as float + } + else + { + // Negative, parse with strtoll and attempt cast to number_integer_t + if (attempt_cast(std::strtoll(reinterpret_cast(m_start), &endptr, 10), result.m_value.number_unsigned)) + result.m_type = value_t::number_integer; + else result.m_type = value_t::number_float; // Cast failed due to overflow - store as float + } - // return float_val if the whole number was translated and NAN - // otherwise - return (reinterpret_cast(endptr) == m_cursor) ? float_val : NAN; + // Check the end of the number was reached and no range error occurred + if (reinterpret_cast(endptr) != m_cursor || errno == ERANGE) result.m_type = value_t::number_float; + + if (result.m_type == value_t::number_float) + { + // Either the number won't fit in an integer (range error from strtoull/strtoll or overflow on cast) or there was + // something else after the number, which could be an exponent + + // Parse with strtod + result.m_value.number_float = str_to_float_t(static_cast(nullptr), &endptr); + + // Anything after the number is an error + if(reinterpret_cast(endptr) != m_cursor) + throw std::invalid_argument(std::string("parse error - ") + get_token() + " is not a number"); + } } private: @@ -7342,32 +7701,8 @@ class basic_json case lexer::token_type::value_number: { - result.m_value = m_lexer.get_number(); - - // NAN is returned if token could not be translated - // completely - if (std::isnan(result.m_value.number_float)) - { - throw std::invalid_argument(std::string("parse error - ") + - m_lexer.get_token() + " is not a number"); - } - + m_lexer.get_number(result); get_token(); - - // check if conversion loses precision (special case -0.0 always loses precision) - const auto int_val = static_cast(result.m_value.number_float); - if (result.m_value.number_float == static_cast(int_val) and - result.m_value.number_integer != json_value(-0.0f).number_integer) - { - // we would not lose precision -> return int - result.m_type = value_t::number_integer; - result.m_value = int_val; - } - else - { - // we would lose precision -> return float - result.m_type = value_t::number_float; - } break; } @@ -7507,3 +7842,5 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t) #endif #endif + + diff --git a/test/unit.cpp b/test/unit.cpp index d800398e90..eb40616037 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -83,6 +83,13 @@ TEST_CASE("constructors") CHECK(j.type() == t); } + SECTION("number_unsigned") + { + auto t = json::value_t::number_unsigned; + json j(t); + CHECK(j.type() == t); + } + SECTION("number_float") { auto t = json::value_t::number_float; @@ -120,7 +127,7 @@ TEST_CASE("constructors") SECTION("filled object") { - json::object_t o {{"a", json(1)}, {"b", json(2.2)}, {"c", json(false)}, {"d", json("string")}, {"e", json()}}; + json::object_t o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; json j(o); CHECK(j.type() == json::value_t::object); } @@ -129,12 +136,12 @@ TEST_CASE("constructors") SECTION("create an object (implicit)") { // reference object - json::object_t o_reference {{"a", json(1)}, {"b", json(2.2)}, {"c", json(false)}, {"d", json("string")}, {"e", json()}}; + json::object_t o_reference {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; json j_reference(o_reference); SECTION("std::map") { - std::map o {{"a", json(1)}, {"b", json(2.2)}, {"c", json(false)}, {"d", json("string")}, {"e", json()}}; + std::map o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; json j(o); CHECK(j.type() == json::value_t::object); CHECK(j == j_reference); @@ -142,7 +149,7 @@ TEST_CASE("constructors") SECTION("std::map") { - std::map o {{"a", json(1)}, {"b", json(2.2)}, {"c", json(false)}, {"d", json("string")}, {"e", json()}}; + std::map o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; json j(o); CHECK(j.type() == json::value_t::object); CHECK(j == j_reference); @@ -150,7 +157,7 @@ TEST_CASE("constructors") SECTION("std::multimap") { - std::multimap o {{"a", json(1)}, {"b", json(2.2)}, {"c", json(false)}, {"d", json("string")}, {"e", json()}}; + std::multimap o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; json j(o); CHECK(j.type() == json::value_t::object); CHECK(j == j_reference); @@ -158,7 +165,7 @@ TEST_CASE("constructors") SECTION("std::unordered_map") { - std::unordered_map o {{"a", json(1)}, {"b", json(2.2)}, {"c", json(false)}, {"d", json("string")}, {"e", json()}}; + std::unordered_map o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; json j(o); CHECK(j.type() == json::value_t::object); CHECK(j == j_reference); @@ -166,7 +173,7 @@ TEST_CASE("constructors") SECTION("std::unordered_multimap") { - std::unordered_multimap o {{"a", json(1)}, {"b", json(2.2)}, {"c", json(false)}, {"d", json("string")}, {"e", json()}}; + std::unordered_multimap o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; json j(o); CHECK(j.type() == json::value_t::object); CHECK(j == j_reference); @@ -174,7 +181,7 @@ TEST_CASE("constructors") SECTION("associative container literal") { - json j({{"a", json(1)}, {"b", json(2.2)}, {"c", json(false)}, {"d", json("string")}, {"e", json()}}); + json j({{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}); CHECK(j.type() == json::value_t::object); CHECK(j == j_reference); } @@ -191,7 +198,7 @@ TEST_CASE("constructors") SECTION("filled array") { - json::array_t a {json(1), json(2.2), json(false), json("string"), json()}; + json::array_t a {json(1), json(1u), json(2.2), json(false), json("string"), json()}; json j(a); CHECK(j.type() == json::value_t::array); } @@ -200,12 +207,12 @@ TEST_CASE("constructors") SECTION("create an array (implicit)") { // reference array - json::array_t a_reference {json(1), json(2.2), json(false), json("string"), json()}; + json::array_t a_reference {json(1), json(1u), json(2.2), json(false), json("string"), json()}; json j_reference(a_reference); SECTION("std::list") { - std::list a {json(1), json(2.2), json(false), json("string"), json()}; + std::list a {json(1), json(1u), json(2.2), json(false), json("string"), json()}; json j(a); CHECK(j.type() == json::value_t::array); CHECK(j == j_reference); @@ -213,7 +220,7 @@ TEST_CASE("constructors") SECTION("std::forward_list") { - std::forward_list a {json(1), json(2.2), json(false), json("string"), json()}; + std::forward_list a {json(1), json(1u), json(2.2), json(false), json("string"), json()}; json j(a); CHECK(j.type() == json::value_t::array); CHECK(j == j_reference); @@ -221,7 +228,7 @@ TEST_CASE("constructors") SECTION("std::array") { - std::array a {{json(1), json(2.2), json(false), json("string"), json()}}; + std::array a {{json(1), json(1u), json(2.2), json(false), json("string"), json()}}; json j(a); CHECK(j.type() == json::value_t::array); CHECK(j == j_reference); @@ -229,7 +236,7 @@ TEST_CASE("constructors") SECTION("std::vector") { - std::vector a {json(1), json(2.2), json(false), json("string"), json()}; + std::vector a {json(1), json(1u), json(2.2), json(false), json("string"), json()}; json j(a); CHECK(j.type() == json::value_t::array); CHECK(j == j_reference); @@ -237,7 +244,7 @@ TEST_CASE("constructors") SECTION("std::deque") { - std::deque a {json(1), json(2.2), json(false), json("string"), json()}; + std::deque a {json(1), json(1u), json(2.2), json(false), json("string"), json()}; json j(a); CHECK(j.type() == json::value_t::array); CHECK(j == j_reference); @@ -245,7 +252,7 @@ TEST_CASE("constructors") SECTION("std::set") { - std::set a {json(1), json(2.2), json(false), json("string"), json()}; + std::set a {json(1), json(1u), json(2.2), json(false), json("string"), json()}; json j(a); CHECK(j.type() == json::value_t::array); // we cannot really check for equality here @@ -253,7 +260,7 @@ TEST_CASE("constructors") SECTION("std::unordered_set") { - std::unordered_set a {json(1), json(2.2), json(false), json("string"), json()}; + std::unordered_set a {json(1), json(1u), json(2.2), json(false), json("string"), json()}; json j(a); CHECK(j.type() == json::value_t::array); // we cannot really check for equality here @@ -261,7 +268,7 @@ TEST_CASE("constructors") SECTION("sequence container literal") { - json j({json(1), json(2.2), json(false), json("string"), json()}); + json j({json(1), json(1u), json(2.2), json(false), json("string"), json()}); CHECK(j.type() == json::value_t::array); CHECK(j == j_reference); } @@ -363,9 +370,11 @@ TEST_CASE("constructors") SECTION("create an integer number (implicit)") { - // reference object + // reference objects json::number_integer_t n_reference = 42; json j_reference(n_reference); + json::number_unsigned_t n_unsigned_reference = 42; + json j_unsigned_reference(n_unsigned_reference); SECTION("short") { @@ -379,8 +388,8 @@ TEST_CASE("constructors") { unsigned short n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("int") @@ -395,8 +404,8 @@ TEST_CASE("constructors") { unsigned int n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("long") @@ -409,10 +418,10 @@ TEST_CASE("constructors") SECTION("unsigned long") { - short n = 42; + unsigned long n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("long long") @@ -427,8 +436,8 @@ TEST_CASE("constructors") { unsigned long long n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("int8_t") @@ -531,96 +540,96 @@ TEST_CASE("constructors") { uint8_t n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("uint16_t") { uint16_t n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("uint32_t") { uint32_t n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("uint64_t") { uint64_t n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("uint_fast8_t") { uint_fast8_t n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("uint_fast16_t") { uint_fast16_t n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("uint_fast32_t") { uint_fast32_t n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("uint_fast64_t") { uint_fast64_t n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("uint_least8_t") { uint_least8_t n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("uint_least16_t") { uint_least16_t n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("uint_least32_t") { uint_least32_t n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("uint_least64_t") { uint_least64_t n = 42; json j(n); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("integer literal without suffix") @@ -633,8 +642,8 @@ TEST_CASE("constructors") SECTION("integer literal with u suffix") { json j(42u); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("integer literal with l suffix") @@ -647,8 +656,8 @@ TEST_CASE("constructors") SECTION("integer literal with ul suffix") { json j(42ul); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } SECTION("integer literal with ll suffix") @@ -661,8 +670,8 @@ TEST_CASE("constructors") SECTION("integer literal with ull suffix") { json j(42ull); - CHECK(j.type() == json::value_t::number_integer); - CHECK(j == j_reference); + CHECK(j.type() == json::value_t::number_unsigned); + CHECK(j == j_unsigned_reference); } } @@ -835,6 +844,22 @@ TEST_CASE("constructors") } } + SECTION("number (unsigned)") + { + SECTION("explicit") + { + std::initializer_list l = {json(1u)}; + json j(l); + CHECK(j.type() == json::value_t::array); + } + + SECTION("implicit") + { + json j {1u}; + CHECK(j.type() == json::value_t::array); + } + } + SECTION("number (floating-point)") { SECTION("explicit") @@ -856,14 +881,14 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {1, 42.23, true, nullptr, json::object_t(), json::array_t()}; + std::initializer_list l = {1, 1u, 42.23, true, nullptr, json::object_t(), json::array_t()}; json j(l); CHECK(j.type() == json::value_t::array); } SECTION("implicit") { - json j {1, 42.23, true, nullptr, json::object_t(), json::array_t()}; + json j {1, 1u, 42.23, true, nullptr, json::object_t(), json::array_t()}; CHECK(j.type() == json::value_t::array); } } @@ -872,13 +897,13 @@ TEST_CASE("constructors") { SECTION("object") { - json j { {"one", 1}, {"two", 2.2}, {"three", false} }; + json j { {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} }; CHECK(j.type() == json::value_t::object); } SECTION("array") { - json j { {"one", 1}, {"two", 2.2}, {"three", false}, 13 }; + json j { {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} , 13 }; CHECK(j.type() == json::value_t::array); } } @@ -893,14 +918,14 @@ TEST_CASE("constructors") SECTION("object") { - json j = json::object({ {"one", 1}, {"two", 2.2}, {"three", false} }); + json j = json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} }); CHECK(j.type() == json::value_t::object); } SECTION("object with error") { - CHECK_THROWS_AS(json::object({ {"one", 1}, {"two", 2.2}, {"three", false}, 13 }), std::logic_error); - CHECK_THROWS_WITH(json::object({ {"one", 1}, {"two", 2.2}, {"three", false}, 13 }), + CHECK_THROWS_AS(json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false}, 13 }), std::logic_error); + CHECK_THROWS_WITH(json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false}, 13 }), "cannot create object from initializer list"); } @@ -912,7 +937,7 @@ TEST_CASE("constructors") SECTION("array") { - json j = json::array({ {"one", 1}, {"two", 2.2}, {"three", false} }); + json j = json::array({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} }); CHECK(j.type() == json::value_t::array); } } @@ -920,7 +945,7 @@ TEST_CASE("constructors") SECTION("create an array of n copies of a given value") { - json v = {1, "foo", 34.23, {1, 2, 3}, {{"A", 1}, {"B", 2}}}; + json v = {1, "foo", 34.23, {1, 2, 3}, {{"A", 1}, {"B", 2u}}}; json arr(3, v); CHECK(arr.size() == 3); for (auto& x : arr) @@ -936,12 +961,12 @@ TEST_CASE("constructors") SECTION("json(begin(), end())") { { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}}; json j_new(jobject.begin(), jobject.end()); CHECK(j_new == jobject); } { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}}; json j_new(jobject.cbegin(), jobject.cend()); CHECK(j_new == jobject); } @@ -950,12 +975,12 @@ TEST_CASE("constructors") SECTION("json(begin(), begin())") { { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}}; json j_new(jobject.begin(), jobject.begin()); CHECK(j_new == json::object()); } { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}}; json j_new(jobject.cbegin(), jobject.cbegin()); CHECK(j_new == json::object()); } @@ -963,24 +988,24 @@ TEST_CASE("constructors") SECTION("construct from subrange") { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}, {"d", false}, {"e", true}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}}; json j_new(jobject.find("b"), jobject.find("e")); - CHECK(j_new == json({{"b", 1}, {"c", 17}, {"d", false}})); + CHECK(j_new == json({{"b", 1}, {"c", 17u}, {"d", false}})); } SECTION("incompatible iterators") { { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}, {"d", false}, {"e", true}}; - json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}}; + json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}}; CHECK_THROWS_AS(json(jobject.begin(), jobject2.end()), std::domain_error); CHECK_THROWS_AS(json(jobject2.begin(), jobject.end()), std::domain_error); CHECK_THROWS_WITH(json(jobject.begin(), jobject2.end()), "iterators are not compatible"); CHECK_THROWS_WITH(json(jobject2.begin(), jobject.end()), "iterators are not compatible"); } { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}, {"d", false}, {"e", true}}; - json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}}; + json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}}; CHECK_THROWS_AS(json(jobject.cbegin(), jobject2.cend()), std::domain_error); CHECK_THROWS_AS(json(jobject2.cbegin(), jobject.cend()), std::domain_error); CHECK_THROWS_WITH(json(jobject.cbegin(), jobject2.cend()), "iterators are not compatible"); @@ -1114,6 +1139,20 @@ TEST_CASE("constructors") } } + SECTION("number (unsigned)") + { + { + json j = 17u; + json j_new(j.begin(), j.end()); + CHECK(j == j_new); + } + { + json j = 17u; + json j_new(j.cbegin(), j.cend()); + CHECK(j == j_new); + } + } + SECTION("number (floating point)") { { @@ -1185,6 +1224,24 @@ TEST_CASE("constructors") } } + SECTION("number (integer)") + { + { + json j = 17u; + CHECK_THROWS_AS(json(j.end(), j.end()), std::out_of_range); + CHECK_THROWS_AS(json(j.begin(), j.begin()), std::out_of_range); + CHECK_THROWS_WITH(json(j.end(), j.end()), "iterators out of range"); + CHECK_THROWS_WITH(json(j.begin(), j.begin()), "iterators out of range"); + } + { + json j = 17u; + CHECK_THROWS_AS(json(j.cend(), j.cend()), std::out_of_range); + CHECK_THROWS_AS(json(j.cbegin(), j.cbegin()), std::out_of_range); + CHECK_THROWS_WITH(json(j.cend(), j.cend()), "iterators out of range"); + CHECK_THROWS_WITH(json(j.cbegin(), j.cbegin()), "iterators out of range"); + } + } + SECTION("number (floating point)") { { @@ -1253,6 +1310,13 @@ TEST_CASE("other constructors and destructor") CHECK(j == k); } + SECTION("number (unsigned)") + { + json j(42u); + json k(j); + CHECK(j == k); + } + SECTION("number (floating-point)") { json j(42.23); @@ -1263,7 +1327,7 @@ TEST_CASE("other constructors and destructor") SECTION("move constructor") { - json j {{"foo", "bar"}, {"baz", {1, 2, 3, 4}}, {"a", 42.23}, {"b", nullptr}}; + json j {{"foo", "bar"}, {"baz", {1, 2, 3, 4}}, {"a", 42u}, {"b", 42.23}, {"c", nullptr}}; CHECK(j.type() == json::value_t::object); json k(std::move(j)); CHECK(k.type() == json::value_t::object); @@ -1320,6 +1384,14 @@ TEST_CASE("other constructors and destructor") CHECK(j == k); } + SECTION("number (unsigned)") + { + json j(42u); + json k; + k = j; + CHECK(j == k); + } + SECTION("number (floating-point)") { json j(42.23); @@ -1339,7 +1411,7 @@ TEST_CASE("other constructors and destructor") SECTION("array") { - auto j = new json {"foo", 1, false, 23.42}; + auto j = new json {"foo", 1, 1u, false, 23.42}; delete j; } @@ -1362,6 +1434,7 @@ TEST_CASE("object inspection") CHECK(not j.is_boolean()); CHECK(not j.is_number()); CHECK(not j.is_number_integer()); + CHECK(not j.is_number_unsigned()); CHECK(not j.is_number_float()); CHECK(j.is_object()); CHECK(not j.is_array()); @@ -1373,11 +1446,12 @@ TEST_CASE("object inspection") SECTION("array") { - json j {"foo", 1, 42.23, false}; + json j {"foo", 1, 1u, 42.23, false}; CHECK(not j.is_null()); CHECK(not j.is_boolean()); CHECK(not j.is_number()); CHECK(not j.is_number_integer()); + CHECK(not j.is_number_unsigned()); CHECK(not j.is_number_float()); CHECK(not j.is_object()); CHECK(j.is_array()); @@ -1394,6 +1468,7 @@ TEST_CASE("object inspection") CHECK(not j.is_boolean()); CHECK(not j.is_number()); CHECK(not j.is_number_integer()); + CHECK(not j.is_number_unsigned()); CHECK(not j.is_number_float()); CHECK(not j.is_object()); CHECK(not j.is_array()); @@ -1410,6 +1485,7 @@ TEST_CASE("object inspection") CHECK(j.is_boolean()); CHECK(not j.is_number()); CHECK(not j.is_number_integer()); + CHECK(not j.is_number_unsigned()); CHECK(not j.is_number_float()); CHECK(not j.is_object()); CHECK(not j.is_array()); @@ -1426,6 +1502,7 @@ TEST_CASE("object inspection") CHECK(not j.is_boolean()); CHECK(not j.is_number()); CHECK(not j.is_number_integer()); + CHECK(not j.is_number_unsigned()); CHECK(not j.is_number_float()); CHECK(not j.is_object()); CHECK(not j.is_array()); @@ -1442,6 +1519,24 @@ TEST_CASE("object inspection") CHECK(not j.is_boolean()); CHECK(j.is_number()); CHECK(j.is_number_integer()); + CHECK(not j.is_number_unsigned()); + CHECK(not j.is_number_float()); + CHECK(not j.is_object()); + CHECK(not j.is_array()); + CHECK(not j.is_string()); + CHECK(not j.is_discarded()); + CHECK(j.is_primitive()); + CHECK(not j.is_structured()); + } + + SECTION("number (unsigned)") + { + json j(42u); + CHECK(not j.is_null()); + CHECK(not j.is_boolean()); + CHECK(j.is_number()); + CHECK(j.is_number_integer()); + CHECK(j.is_number_unsigned()); CHECK(not j.is_number_float()); CHECK(not j.is_object()); CHECK(not j.is_array()); @@ -1458,6 +1553,7 @@ TEST_CASE("object inspection") CHECK(not j.is_boolean()); CHECK(j.is_number()); CHECK(not j.is_number_integer()); + CHECK(not j.is_number_unsigned()); CHECK(j.is_number_float()); CHECK(not j.is_object()); CHECK(not j.is_array()); @@ -1474,6 +1570,7 @@ TEST_CASE("object inspection") CHECK(not j.is_boolean()); CHECK(not j.is_number()); CHECK(not j.is_number_integer()); + CHECK(not j.is_number_unsigned()); CHECK(not j.is_number_float()); CHECK(not j.is_object()); CHECK(not j.is_array()); @@ -1572,6 +1669,12 @@ TEST_CASE("object inspection") CHECK(j.type() == json::value_t::number_integer); } + SECTION("number (unsigned)") + { + json j = 23u; + CHECK(j.type() == json::value_t::number_unsigned); + } + SECTION("number (floating-point)") { json j = 42.23; @@ -1623,6 +1726,13 @@ TEST_CASE("object inspection") CHECK(t == j.type()); } + SECTION("number (unsigned)") + { + json j = 23u; + json::value_t t = j; + CHECK(t == j.type()); + } + SECTION("number (floating-point)") { json j = 42.23; @@ -1677,6 +1787,7 @@ TEST_CASE("value conversion") CHECK_THROWS_AS(json(json::value_t::string).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::boolean).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_integer).get(), std::logic_error); + CHECK_THROWS_AS(json(json::value_t::number_unsigned).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_float).get(), std::logic_error); CHECK_THROWS_WITH(json(json::value_t::null).get(), @@ -1689,6 +1800,8 @@ TEST_CASE("value conversion") "type must be object, but is boolean"); CHECK_THROWS_WITH(json(json::value_t::number_integer).get(), "type must be object, but is number"); + CHECK_THROWS_WITH(json(json::value_t::number_unsigned).get(), + "type must be object, but is number"); CHECK_THROWS_WITH(json(json::value_t::number_float).get(), "type must be object, but is number"); } @@ -1732,7 +1845,7 @@ TEST_CASE("value conversion") SECTION("get an array (explicit)") { - json::array_t a_reference {json(1), json(2.2), json(false), json("string"), json()}; + json::array_t a_reference {json(1), json(1u), json(2.2), json(false), json("string"), json()}; json j(a_reference); SECTION("json::array_t") @@ -1772,6 +1885,7 @@ TEST_CASE("value conversion") CHECK_THROWS_AS(json(json::value_t::string).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::boolean).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_integer).get(), std::logic_error); + CHECK_THROWS_AS(json(json::value_t::number_unsigned).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_float).get(), std::logic_error); CHECK_THROWS_WITH(json(json::value_t::null).get(), @@ -1784,6 +1898,8 @@ TEST_CASE("value conversion") "type must be array, but is boolean"); CHECK_THROWS_WITH(json(json::value_t::number_integer).get(), "type must be array, but is number"); + CHECK_THROWS_WITH(json(json::value_t::number_unsigned).get(), + "type must be array, but is number"); CHECK_THROWS_WITH(json(json::value_t::number_float).get(), "type must be array, but is number"); } @@ -1791,7 +1907,7 @@ TEST_CASE("value conversion") SECTION("get an array (implicit)") { - json::array_t a_reference {json(1), json(2.2), json(false), json("string"), json()}; + json::array_t a_reference {json(1), json(1u), json(2.2), json(false), json("string"), json()}; json j(a_reference); SECTION("json::array_t") @@ -1849,6 +1965,7 @@ TEST_CASE("value conversion") CHECK_THROWS_AS(json(json::value_t::array).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::boolean).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_integer).get(), std::logic_error); + CHECK_THROWS_AS(json(json::value_t::number_unsigned).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_float).get(), std::logic_error); CHECK_THROWS_WITH(json(json::value_t::null).get(), @@ -1861,6 +1978,8 @@ TEST_CASE("value conversion") "type must be string, but is boolean"); CHECK_THROWS_WITH(json(json::value_t::number_integer).get(), "type must be string, but is number"); + CHECK_THROWS_WITH(json(json::value_t::number_unsigned).get(), + "type must be string, but is number"); CHECK_THROWS_WITH(json(json::value_t::number_float).get(), "type must be string, but is number"); } @@ -1908,6 +2027,7 @@ TEST_CASE("value conversion") CHECK_THROWS_AS(json(json::value_t::array).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::string).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_integer).get(), std::logic_error); + CHECK_THROWS_AS(json(json::value_t::number_unsigned).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_float).get(), std::logic_error); CHECK_THROWS_WITH(json(json::value_t::null).get(), @@ -1920,6 +2040,8 @@ TEST_CASE("value conversion") "type must be boolean, but is string"); CHECK_THROWS_WITH(json(json::value_t::number_integer).get(), "type must be boolean, but is number"); + CHECK_THROWS_WITH(json(json::value_t::number_unsigned).get(), + "type must be boolean, but is number"); CHECK_THROWS_WITH(json(json::value_t::number_float).get(), "type must be boolean, but is number"); } @@ -1947,6 +2069,8 @@ TEST_CASE("value conversion") { json::number_integer_t n_reference {42}; json j(n_reference); + json::number_unsigned_t n_unsigned_reference {42u}; + json j_unsigned(n_unsigned_reference); SECTION("number_integer_t") { @@ -1954,6 +2078,12 @@ TEST_CASE("value conversion") CHECK(json(n) == j); } + SECTION("number_unsigned_t") + { + json::number_unsigned_t n = j_unsigned.get(); + CHECK(json(n) == j_unsigned); + } + SECTION("short") { short n = j.get(); @@ -2166,6 +2296,7 @@ TEST_CASE("value conversion") "type must be number, but is boolean"); CHECK_NOTHROW(json(json::value_t::number_float).get()); + CHECK_NOTHROW(json(json::value_t::number_float).get()); } } @@ -2173,12 +2304,20 @@ TEST_CASE("value conversion") { json::number_integer_t n_reference {42}; json j(n_reference); + json::number_unsigned_t n_unsigned_reference {42u}; + json j_unsigned(n_unsigned_reference); SECTION("number_integer_t") { json::number_integer_t n = j.get(); CHECK(json(n) == j); } + + SECTION("number_unsigned_t") + { + json::number_unsigned_t n = j_unsigned.get(); + CHECK(json(n) == j_unsigned); + } SECTION("short") { @@ -2188,8 +2327,8 @@ TEST_CASE("value conversion") SECTION("unsigned short") { - unsigned short n = j; - CHECK(json(n) == j); + unsigned short n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("int") @@ -2200,8 +2339,8 @@ TEST_CASE("value conversion") SECTION("unsigned int") { - unsigned int n = j; - CHECK(json(n) == j); + unsigned int n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("long") @@ -2212,8 +2351,8 @@ TEST_CASE("value conversion") SECTION("unsigned long") { - unsigned long n = j; - CHECK(json(n) == j); + unsigned long n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("long long") @@ -2224,8 +2363,8 @@ TEST_CASE("value conversion") SECTION("unsigned long long") { - unsigned long long n = j; - CHECK(json(n) == j); + unsigned long long n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("int8_t") @@ -2302,74 +2441,74 @@ TEST_CASE("value conversion") SECTION("uint8_t") { - uint8_t n = j; - CHECK(json(n) == j); + uint8_t n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("uint16_t") { - uint16_t n = j; - CHECK(json(n) == j); + uint16_t n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("uint32_t") { - uint32_t n = j; - CHECK(json(n) == j); + uint32_t n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("uint64_t") { - uint64_t n = j; - CHECK(json(n) == j); + uint64_t n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("uint8_fast_t") { - uint_fast8_t n = j; - CHECK(json(n) == j); + uint_fast8_t n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("uint16_fast_t") { - uint_fast16_t n = j; - CHECK(json(n) == j); + uint_fast16_t n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("uint32_fast_t") { - uint_fast32_t n = j; - CHECK(json(n) == j); + uint_fast32_t n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("uint64_fast_t") { - uint_fast64_t n = j; - CHECK(json(n) == j); + uint_fast64_t n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("uint8_least_t") { - uint_least8_t n = j; - CHECK(json(n) == j); + uint_least8_t n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("uint16_least_t") { - uint_least16_t n = j; - CHECK(json(n) == j); + uint_least16_t n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("uint32_least_t") { - uint_least32_t n = j; - CHECK(json(n) == j); + uint_least32_t n = j_unsigned; + CHECK(json(n) == j_unsigned); } SECTION("uint64_least_t") { - uint_least64_t n = j; - CHECK(json(n) == j); + uint_least64_t n = j_unsigned; + CHECK(json(n) == j_unsigned); } } @@ -2416,6 +2555,7 @@ TEST_CASE("value conversion") "type must be number, but is boolean"); CHECK_NOTHROW(json(json::value_t::number_integer).get()); + CHECK_NOTHROW(json(json::value_t::number_unsigned).get()); } } @@ -2448,43 +2588,48 @@ TEST_CASE("value conversion") SECTION("object-like STL containers") { json j1 = {{"one", 1}, {"two", 2}, {"three", 3}}; - json j2 = {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}; - json j3 = {{"one", true}, {"two", false}, {"three", true}}; - json j4 = {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}; + json j2 = {{"one", 1u}, {"two", 2u}, {"three", 3u}}; + json j3 = {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}; + json j4 = {{"one", true}, {"two", false}, {"three", true}}; + json j5 = {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}; SECTION("std::map") { auto m1 = j1.get>(); - auto m2 = j2.get>(); - auto m3 = j3.get>(); - //auto m4 = j4.get>(); + auto m2 = j2.get>(); + auto m3 = j3.get>(); + auto m4 = j4.get>(); + //auto m5 = j5.get>(); } SECTION("std::unordered_map") { auto m1 = j1.get>(); - auto m2 = j2.get>(); - auto m3 = j3.get>(); - //auto m4 = j4.get>(); - //CHECK(m4["one"] == "eins"); + auto m2 = j2.get>(); + auto m3 = j3.get>(); + auto m4 = j4.get>(); + //auto m5 = j5.get>(); + //CHECK(m5["one"] == "eins"); } SECTION("std::multimap") { auto m1 = j1.get>(); - auto m2 = j2.get>(); - auto m3 = j3.get>(); - //auto m4 = j4.get>(); - //CHECK(m4["one"] == "eins"); + auto m2 = j2.get>(); + auto m3 = j3.get>(); + auto m4 = j4.get>(); + //auto m5 = j5.get>(); + //CHECK(m5["one"] == "eins"); } SECTION("std::unordered_multimap") { auto m1 = j1.get>(); - auto m2 = j2.get>(); - auto m3 = j3.get>(); - //auto m4 = j4.get>(); - //CHECK(m4["one"] == "eins"); + auto m2 = j2.get>(); + auto m3 = j3.get>(); + auto m4 = j4.get>(); + //auto m5 = j5.get>(); + //CHECK(m5["one"] == "eins"); } SECTION("exception in case of a non-object type") @@ -2497,56 +2642,63 @@ TEST_CASE("value conversion") SECTION("array-like STL containers") { json j1 = {1, 2, 3, 4}; - json j2 = {1.2, 2.3, 3.4, 4.5}; - json j3 = {true, false, true}; - json j4 = {"one", "two", "three"}; + json j2 = {1u, 2u, 3u, 4u}; + json j3 = {1.2, 2.3, 3.4, 4.5}; + json j4 = {true, false, true}; + json j5 = {"one", "two", "three"}; SECTION("std::list") { auto m1 = j1.get>(); - auto m2 = j2.get>(); - auto m3 = j3.get>(); - auto m4 = j4.get>(); + auto m2 = j2.get>(); + auto m3 = j3.get>(); + auto m4 = j4.get>(); + auto m5 = j5.get>(); } //SECTION("std::forward_list") //{ // auto m1 = j1.get>(); - // auto m2 = j2.get>(); - // auto m3 = j3.get>(); - // auto m4 = j4.get>(); + // auto m2 = j2.get>(); + // auto m3 = j3.get>(); + // auto m4 = j4.get>(); + // auto m5 = j5.get>(); //} SECTION("std::vector") { auto m1 = j1.get>(); - auto m2 = j2.get>(); - auto m3 = j3.get>(); - auto m4 = j4.get>(); + auto m2 = j2.get>(); + auto m3 = j3.get>(); + auto m4 = j4.get>(); + auto m5 = j5.get>(); } SECTION("std::deque") { auto m1 = j1.get>(); - auto m2 = j2.get>(); - auto m3 = j3.get>(); - auto m4 = j4.get>(); + auto m2 = j2.get>(); + auto m3 = j2.get>(); + auto m4 = j4.get>(); + auto m5 = j5.get>(); } SECTION("std::set") { auto m1 = j1.get>(); - auto m2 = j2.get>(); - auto m3 = j3.get>(); - auto m4 = j4.get>(); + auto m2 = j2.get>(); + auto m3 = j3.get>(); + auto m4 = j4.get>(); + auto m5 = j5.get>(); } SECTION("std::unordered_set") { auto m1 = j1.get>(); - auto m2 = j2.get>(); - auto m3 = j3.get>(); - auto m4 = j4.get>(); + auto m2 = j2.get>(); + auto m3 = j3.get>(); + auto m4 = j4.get>(); + auto m5 = j5.get>(); } SECTION("exception in case of a non-object type") @@ -2574,6 +2726,7 @@ TEST_CASE("pointer access") { "number", { {"integer", 42}, + {"unsigned", 42u}, {"floating-point", 17.23} } }, @@ -2606,6 +2759,7 @@ TEST_CASE("pointer access") CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() == nullptr); + CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() == nullptr); } @@ -2649,6 +2803,7 @@ TEST_CASE("pointer access") CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() == nullptr); + CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() == nullptr); } @@ -2676,6 +2831,7 @@ TEST_CASE("pointer access") CHECK(value.get_ptr() != nullptr); CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() == nullptr); + CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() == nullptr); } @@ -2703,6 +2859,7 @@ TEST_CASE("pointer access") CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() != nullptr); CHECK(value.get_ptr() == nullptr); + CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() == nullptr); } @@ -2730,6 +2887,35 @@ TEST_CASE("pointer access") CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() != nullptr); + CHECK(value.get_ptr() == nullptr); + CHECK(value.get_ptr() == nullptr); + } + + SECTION("pointer access to number_unsigned_t") + { + using test_type = json::number_unsigned_t; + json value = 23u; + + // check if pointers are returned correctly + test_type* p1 = value.get_ptr(); + CHECK(p1 == value.get_ptr()); + CHECK(*p1 == value.get()); + + const test_type* p2 = value.get_ptr(); + CHECK(p1 == value.get_ptr()); + CHECK(*p2 == value.get()); + + const test_type* const p3 = value.get_ptr(); + CHECK(p1 == value.get_ptr()); + CHECK(*p3 == value.get()); + + // check if null pointers are returned correctly + CHECK(value.get_ptr() == nullptr); + CHECK(value.get_ptr() == nullptr); + CHECK(value.get_ptr() == nullptr); + CHECK(value.get_ptr() == nullptr); + CHECK(value.get_ptr() != nullptr); + CHECK(value.get_ptr() != nullptr); CHECK(value.get_ptr() == nullptr); } @@ -2757,6 +2943,7 @@ TEST_CASE("pointer access") CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() == nullptr); + CHECK(value.get_ptr() == nullptr); CHECK(value.get_ptr() != nullptr); } } @@ -2935,7 +3122,7 @@ TEST_CASE("element access") { SECTION("array") { - json j = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json j = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; const json j_const = j; SECTION("access specified element with bounds checking") @@ -2943,29 +3130,31 @@ TEST_CASE("element access") SECTION("access within bounds") { CHECK(j.at(0) == json(1)); - CHECK(j.at(1) == json(true)); - CHECK(j.at(2) == json(nullptr)); - CHECK(j.at(3) == json("string")); - CHECK(j.at(4) == json(42.23)); - CHECK(j.at(5) == json(json::object())); - CHECK(j.at(6) == json({1, 2, 3})); + CHECK(j.at(1) == json(1u)); + CHECK(j.at(2) == json(true)); + CHECK(j.at(3) == json(nullptr)); + CHECK(j.at(4) == json("string")); + CHECK(j.at(5) == json(42.23)); + CHECK(j.at(6) == json(json::object())); + CHECK(j.at(7) == json({1, 2, 3})); CHECK(j_const.at(0) == json(1)); - CHECK(j_const.at(1) == json(true)); - CHECK(j_const.at(2) == json(nullptr)); - CHECK(j_const.at(3) == json("string")); - CHECK(j_const.at(4) == json(42.23)); - CHECK(j_const.at(5) == json(json::object())); - CHECK(j_const.at(6) == json({1, 2, 3})); + CHECK(j_const.at(1) == json(1u)); + CHECK(j_const.at(2) == json(true)); + CHECK(j_const.at(3) == json(nullptr)); + CHECK(j_const.at(4) == json("string")); + CHECK(j_const.at(5) == json(42.23)); + CHECK(j_const.at(6) == json(json::object())); + CHECK(j_const.at(7) == json({1, 2, 3})); } SECTION("access outside bounds") { - CHECK_THROWS_AS(j.at(7), std::out_of_range); - CHECK_THROWS_AS(j_const.at(7), std::out_of_range); + CHECK_THROWS_AS(j.at(8), std::out_of_range); + CHECK_THROWS_AS(j_const.at(8), std::out_of_range); - CHECK_THROWS_WITH(j.at(7), "array index 7 is out of range"); - CHECK_THROWS_WITH(j_const.at(7), "array index 7 is out of range"); + CHECK_THROWS_WITH(j.at(8), "array index 8 is out of range"); + CHECK_THROWS_WITH(j_const.at(8), "array index 8 is out of range"); } SECTION("access on non-array type") @@ -3025,6 +3214,17 @@ TEST_CASE("element access") CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with number"); } + SECTION("number (unsigned)") + { + json j_nonarray(json::value_t::number_unsigned); + const json j_nonarray_const(j_nonarray); + CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error); + CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error); + + CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with number"); + CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with number"); + } + SECTION("number (floating-point)") { json j_nonarray(json::value_t::number_float); @@ -3051,20 +3251,22 @@ TEST_CASE("element access") SECTION("access within bounds") { CHECK(j[0] == json(1)); - CHECK(j[1] == json(true)); - CHECK(j[2] == json(nullptr)); - CHECK(j[3] == json("string")); - CHECK(j[4] == json(42.23)); - CHECK(j[5] == json(json::object())); - CHECK(j[6] == json({1, 2, 3})); + CHECK(j[1] == json(1u)); + CHECK(j[2] == json(true)); + CHECK(j[3] == json(nullptr)); + CHECK(j[4] == json("string")); + CHECK(j[5] == json(42.23)); + CHECK(j[6] == json(json::object())); + CHECK(j[7] == json({1, 2, 3})); CHECK(j_const[0] == json(1)); - CHECK(j_const[1] == json(true)); - CHECK(j_const[2] == json(nullptr)); - CHECK(j_const[3] == json("string")); - CHECK(j_const[4] == json(42.23)); - CHECK(j_const[5] == json(json::object())); - CHECK(j_const[6] == json({1, 2, 3})); + CHECK(j_const[1] == json(1u)); + CHECK(j_const[2] == json(true)); + CHECK(j_const[3] == json(nullptr)); + CHECK(j_const[4] == json("string")); + CHECK(j_const[5] == json(42.23)); + CHECK(j_const[6] == json(json::object())); + CHECK(j_const[7] == json({1, 2, 3})); } SECTION("access on non-array type") @@ -3128,6 +3330,16 @@ TEST_CASE("element access") CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with number"); } + SECTION("number (unsigned)") + { + json j_nonarray(json::value_t::number_unsigned); + const json j_nonarray_const(j_nonarray); + CHECK_THROWS_AS(j_nonarray[0], std::domain_error); + CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error); + CHECK_THROWS_WITH(j_nonarray[0], "cannot use operator[] with number"); + CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with number"); + } + SECTION("number (floating-point)") { json j_nonarray(json::value_t::number_float); @@ -3145,44 +3357,49 @@ TEST_CASE("element access") SECTION("remove element by index") { { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; jarray.erase(0); - CHECK(jarray == json({true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); + CHECK(jarray == json({1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); } { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; jarray.erase(1); - CHECK(jarray == json({1, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); + CHECK(jarray == json({1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); } { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; jarray.erase(2); - CHECK(jarray == json({1, true, "string", 42.23, json::object(), {1, 2, 3}})); + CHECK(jarray == json({1, 1u, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); } { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; jarray.erase(3); - CHECK(jarray == json({1, true, nullptr, 42.23, json::object(), {1, 2, 3}})); + CHECK(jarray == json({1, 1u, true, "string", 42.23, json::object(), {1, 2, 3}})); } { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; jarray.erase(4); - CHECK(jarray == json({1, true, nullptr, "string", json::object(), {1, 2, 3}})); + CHECK(jarray == json({1, 1u, true, nullptr, 42.23, json::object(), {1, 2, 3}})); } { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; jarray.erase(5); - CHECK(jarray == json({1, true, nullptr, "string", 42.23, {1, 2, 3}})); + CHECK(jarray == json({1, 1u, true, nullptr, "string", json::object(), {1, 2, 3}})); } { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; jarray.erase(6); - CHECK(jarray == json({1, true, nullptr, "string", 42.23, json::object()})); + CHECK(jarray == json({1, 1u, true, nullptr, "string", 42.23, {1, 2, 3}})); + } + { + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + jarray.erase(7); + CHECK(jarray == json({1, 1u, true, nullptr, "string", 42.23, json::object()})); } { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; - CHECK_THROWS_AS(jarray.erase(7), std::out_of_range); - CHECK_THROWS_WITH(jarray.erase(7), "index out of range"); + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + CHECK_THROWS_AS(jarray.erase(8), std::out_of_range); + CHECK_THROWS_WITH(jarray.erase(8), "index out of range"); } } @@ -3191,29 +3408,29 @@ TEST_CASE("element access") SECTION("erase(begin())") { { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; json::iterator it2 = jarray.erase(jarray.begin()); - CHECK(jarray == json({true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); - CHECK(*it2 == json(true)); + CHECK(jarray == json({1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); + CHECK(*it2 == json(1u)); } { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; json::const_iterator it2 = jarray.erase(jarray.cbegin()); - CHECK(jarray == json({true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); - CHECK(*it2 == json(true)); + CHECK(jarray == json({1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); + CHECK(*it2 == json(1u)); } } SECTION("erase(begin(), end())") { { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; json::iterator it2 = jarray.erase(jarray.begin(), jarray.end()); CHECK(jarray == json::array()); CHECK(it2 == jarray.end()); } { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; json::const_iterator it2 = jarray.erase(jarray.cbegin(), jarray.cend()); CHECK(jarray == json::array()); CHECK(it2 == jarray.cend()); @@ -3223,15 +3440,15 @@ TEST_CASE("element access") SECTION("erase(begin(), begin())") { { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; json::iterator it2 = jarray.erase(jarray.begin(), jarray.begin()); - CHECK(jarray == json({1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); + CHECK(jarray == json({1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); CHECK(*it2 == json(1)); } { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; json::const_iterator it2 = jarray.erase(jarray.cbegin(), jarray.cbegin()); - CHECK(jarray == json({1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); + CHECK(jarray == json({1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); CHECK(*it2 == json(1)); } } @@ -3239,17 +3456,17 @@ TEST_CASE("element access") SECTION("erase at offset") { { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; - json::iterator it = jarray.begin() + 3; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::iterator it = jarray.begin() + 4; json::iterator it2 = jarray.erase(it); - CHECK(jarray == json({1, true, nullptr, 42.23, json::object(), {1, 2, 3}})); + CHECK(jarray == json({1, 1u, true, nullptr, 42.23, json::object(), {1, 2, 3}})); CHECK(*it2 == json(42.23)); } { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; - json::const_iterator it = jarray.cbegin() + 3; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::const_iterator it = jarray.cbegin() + 4; json::const_iterator it2 = jarray.erase(it); - CHECK(jarray == json({1, true, nullptr, 42.23, json::object(), {1, 2, 3}})); + CHECK(jarray == json({1, 1u, true, nullptr, 42.23, json::object(), {1, 2, 3}})); CHECK(*it2 == json(42.23)); } } @@ -3257,15 +3474,15 @@ TEST_CASE("element access") SECTION("erase subrange") { { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; - json::iterator it2 = jarray.erase(jarray.begin() + 2, jarray.begin() + 5); - CHECK(jarray == json({1, true, json::object(), {1, 2, 3}})); + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::iterator it2 = jarray.erase(jarray.begin() + 3, jarray.begin() + 6); + CHECK(jarray == json({1, 1u, true, json::object(), {1, 2, 3}})); CHECK(*it2 == json::object()); } { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; - json::const_iterator it2 = jarray.erase(jarray.cbegin() + 2, jarray.cbegin() + 5); - CHECK(jarray == json({1, true, json::object(), {1, 2, 3}})); + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::const_iterator it2 = jarray.erase(jarray.cbegin() + 3, jarray.cbegin() + 6); + CHECK(jarray == json({1, 1u, true, json::object(), {1, 2, 3}})); CHECK(*it2 == json::object()); } } @@ -3273,7 +3490,7 @@ TEST_CASE("element access") SECTION("different arrays") { { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; json jarray2 = {"foo", "bar"}; CHECK_THROWS_AS(jarray.erase(jarray2.begin()), std::domain_error); CHECK_THROWS_AS(jarray.erase(jarray.begin(), jarray2.end()), std::domain_error); @@ -3289,7 +3506,7 @@ TEST_CASE("element access") "iterators do not fit current value"); } { - json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; json jarray2 = {"foo", "bar"}; CHECK_THROWS_AS(jarray.erase(jarray2.cbegin()), std::domain_error); CHECK_THROWS_AS(jarray.erase(jarray.cbegin(), jarray2.cend()), std::domain_error); @@ -3344,6 +3561,13 @@ TEST_CASE("element access") CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with number"); } + SECTION("number (unsigned)") + { + json j_nonobject(json::value_t::number_unsigned); + CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with number"); + } + SECTION("number (floating-point)") { json j_nonobject(json::value_t::number_float); @@ -3356,7 +3580,7 @@ TEST_CASE("element access") SECTION("object") { - json j = {{"integer", 1}, {"floating", 42.23}, {"null", nullptr}, {"string", "hello world"}, {"boolean", true}, {"object", json::object()}, {"array", {1, 2, 3}}}; + json j = {{"integer", 1}, {"unsigned", 1u}, {"floating", 42.23}, {"null", nullptr}, {"string", "hello world"}, {"boolean", true}, {"object", json::object()}, {"array", {1, 2, 3}}}; const json j_const = j; SECTION("access specified element with bounds checking") @@ -3364,6 +3588,7 @@ TEST_CASE("element access") SECTION("access within bounds") { CHECK(j.at("integer") == json(1)); + CHECK(j.at("unsigned") == json(1u)); CHECK(j.at("boolean") == json(true)); CHECK(j.at("null") == json(nullptr)); CHECK(j.at("string") == json("hello world")); @@ -3372,6 +3597,7 @@ TEST_CASE("element access") CHECK(j.at("array") == json({1, 2, 3})); CHECK(j_const.at("integer") == json(1)); + CHECK(j_const.at("unsigned") == json(1u)); CHECK(j_const.at("boolean") == json(true)); CHECK(j_const.at("null") == json(nullptr)); CHECK(j_const.at("string") == json("hello world")); @@ -3439,7 +3665,17 @@ TEST_CASE("element access") CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with number"); CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with number"); } - + + SECTION("number (unsigned)") + { + json j_nonobject(json::value_t::number_unsigned); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with number"); + CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with number"); + } + SECTION("number (floating-point)") { json j_nonobject(json::value_t::number_float); @@ -3458,6 +3694,8 @@ TEST_CASE("element access") { CHECK(j.value("integer", 2) == 1); CHECK(j.value("integer", 1.0) == Approx(1)); + CHECK(j.value("unsigned", 2) == 1u); + CHECK(j.value("unsigned", 1.0) == Approx(1u)); CHECK(j.value("null", json(1)) == json()); CHECK(j.value("boolean", false) == true); CHECK(j.value("string", "bar") == "hello world"); @@ -3469,6 +3707,8 @@ TEST_CASE("element access") CHECK(j_const.value("integer", 2) == 1); CHECK(j_const.value("integer", 1.0) == Approx(1)); + CHECK(j_const.value("unsigned", 2) == 1u); + CHECK(j_const.value("unsigned", 1.0) == Approx(1u)); CHECK(j_const.value("boolean", false) == true); CHECK(j_const.value("string", "bar") == "hello world"); CHECK(j_const.value("string", std::string("bar")) == "hello world"); @@ -3481,6 +3721,7 @@ TEST_CASE("element access") SECTION("access non-existing value") { CHECK(j.value("_", 2) == 2); + CHECK(j.value("_", 2u) == 2u); CHECK(j.value("_", false) == false); CHECK(j.value("_", "bar") == "bar"); CHECK(j.value("_", 12.34) == Approx(12.34)); @@ -3488,6 +3729,7 @@ TEST_CASE("element access") CHECK(j.value("_", json({10, 100})) == json({10, 100})); CHECK(j_const.value("_", 2) == 2); + CHECK(j_const.value("_", 2u) == 2u); CHECK(j_const.value("_", false) == false); CHECK(j_const.value("_", "bar") == "bar"); CHECK(j_const.value("_", 12.34) == Approx(12.34)); @@ -3547,6 +3789,16 @@ TEST_CASE("element access") CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with number"); } + SECTION("number (unsigned)") + { + json j_nonobject(json::value_t::number_unsigned); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with number"); + CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with number"); + } + SECTION("number (floating-point)") { json j_nonobject(json::value_t::number_float); @@ -3564,9 +3816,9 @@ TEST_CASE("element access") // "array" is the smallest key CHECK(j.front() == json({1, 2, 3})); CHECK(j_const.front() == json({1, 2, 3})); - // "string" is the largest key - CHECK(j.back() == json("hello world")); - CHECK(j_const.back() == json("hello world")); + // "unsigned" is the largest key + CHECK(j.back() == json(1u)); + CHECK(j_const.back() == json(1u)); } SECTION("access specified element") @@ -3576,6 +3828,9 @@ TEST_CASE("element access") CHECK(j["integer"] == json(1)); CHECK(j[json::object_t::key_type("integer")] == j["integer"]); + CHECK(j["unsigned"] == json(1u)); + CHECK(j[json::object_t::key_type("unsigned")] == j["unsigned"]); + CHECK(j["boolean"] == json(true)); CHECK(j[json::object_t::key_type("boolean")] == j["boolean"]); @@ -3695,6 +3950,22 @@ TEST_CASE("element access") "cannot use operator[] with number"); } + SECTION("number (unsigned)") + { + json j_nonobject(json::value_t::number_unsigned); + const json j_const_nonobject(j_nonobject); + CHECK_THROWS_AS(j_nonobject["foo"], std::domain_error); + CHECK_THROWS_AS(j_nonobject[json::object_t::key_type("foo")], std::domain_error); + CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error); + CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error); + CHECK_THROWS_WITH(j_nonobject["foo"], "cannot use operator[] with number"); + CHECK_THROWS_WITH(j_nonobject[json::object_t::key_type("foo")], + "cannot use operator[] with number"); + CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with number"); + CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")], + "cannot use operator[] with number"); + } + SECTION("number (floating-point)") { json j_nonobject(json::value_t::number_float); @@ -3722,6 +3993,11 @@ TEST_CASE("element access") CHECK(j.find("integer") == j.end()); CHECK(j.erase("integer") == 0); + CHECK(j.find("unsigned") != j.end()); + CHECK(j.erase("unsigned") == 1); + CHECK(j.find("unsigned") == j.end()); + CHECK(j.erase("unsigned") == 0); + CHECK(j.find("boolean") != j.end()); CHECK(j.erase("boolean") == 1); CHECK(j.find("boolean") == j.end()); @@ -3758,15 +4034,15 @@ TEST_CASE("element access") SECTION("erase(begin())") { { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}}; json::iterator it2 = jobject.erase(jobject.begin()); - CHECK(jobject == json({{"b", 1}, {"c", 17}})); + CHECK(jobject == json({{"b", 1}, {"c", 17u}})); CHECK(*it2 == json(1)); } { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}}; json::const_iterator it2 = jobject.erase(jobject.cbegin()); - CHECK(jobject == json({{"b", 1}, {"c", 17}})); + CHECK(jobject == json({{"b", 1}, {"c", 17u}})); CHECK(*it2 == json(1)); } } @@ -3774,13 +4050,13 @@ TEST_CASE("element access") SECTION("erase(begin(), end())") { { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}}; json::iterator it2 = jobject.erase(jobject.begin(), jobject.end()); CHECK(jobject == json::object()); CHECK(it2 == jobject.end()); } { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}}; json::const_iterator it2 = jobject.erase(jobject.cbegin(), jobject.cend()); CHECK(jobject == json::object()); CHECK(it2 == jobject.cend()); @@ -3790,15 +4066,15 @@ TEST_CASE("element access") SECTION("erase(begin(), begin())") { { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}}; json::iterator it2 = jobject.erase(jobject.begin(), jobject.begin()); - CHECK(jobject == json({{"a", "a"}, {"b", 1}, {"c", 17}})); + CHECK(jobject == json({{"a", "a"}, {"b", 1}, {"c", 17u}})); CHECK(*it2 == json("a")); } { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}}; json::const_iterator it2 = jobject.erase(jobject.cbegin(), jobject.cbegin()); - CHECK(jobject == json({{"a", "a"}, {"b", 1}, {"c", 17}})); + CHECK(jobject == json({{"a", "a"}, {"b", 1}, {"c", 17u}})); CHECK(*it2 == json("a")); } } @@ -3806,17 +4082,17 @@ TEST_CASE("element access") SECTION("erase at offset") { { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}}; json::iterator it = jobject.find("b"); json::iterator it2 = jobject.erase(it); - CHECK(jobject == json({{"a", "a"}, {"c", 17}})); + CHECK(jobject == json({{"a", "a"}, {"c", 17u}})); CHECK(*it2 == json(17)); } { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}}; json::const_iterator it = jobject.find("b"); json::const_iterator it2 = jobject.erase(it); - CHECK(jobject == json({{"a", "a"}, {"c", 17}})); + CHECK(jobject == json({{"a", "a"}, {"c", 17u}})); CHECK(*it2 == json(17)); } } @@ -3824,13 +4100,13 @@ TEST_CASE("element access") SECTION("erase subrange") { { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}, {"d", false}, {"e", true}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}}; json::iterator it2 = jobject.erase(jobject.find("b"), jobject.find("e")); CHECK(jobject == json({{"a", "a"}, {"e", true}})); CHECK(*it2 == json(true)); } { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}, {"d", false}, {"e", true}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}}; json::const_iterator it2 = jobject.erase(jobject.find("b"), jobject.find("e")); CHECK(jobject == json({{"a", "a"}, {"e", true}})); CHECK(*it2 == json(true)); @@ -3840,8 +4116,8 @@ TEST_CASE("element access") SECTION("different objects") { { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}, {"d", false}, {"e", true}}; - json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}}; + json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}}; CHECK_THROWS_AS(jobject.erase(jobject2.begin()), std::domain_error); CHECK_THROWS_AS(jobject.erase(jobject.begin(), jobject2.end()), std::domain_error); CHECK_THROWS_AS(jobject.erase(jobject2.begin(), jobject.end()), std::domain_error); @@ -3855,8 +4131,8 @@ TEST_CASE("element access") "iterators do not fit current value"); } { - json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}, {"d", false}, {"e", true}}; - json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}}; + json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}}; CHECK_THROWS_AS(jobject.erase(jobject2.cbegin()), std::domain_error); CHECK_THROWS_AS(jobject.erase(jobject.cbegin(), jobject2.cend()), std::domain_error); CHECK_THROWS_AS(jobject.erase(jobject2.cbegin(), jobject.cend()), std::domain_error); @@ -3923,7 +4199,7 @@ TEST_CASE("element access") SECTION("existing element") { for (auto key : - {"integer", "floating", "null", "string", "boolean", "object", "array" + {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array" }) { CHECK(j.find(key) != j.end()); @@ -3989,6 +4265,14 @@ TEST_CASE("element access") CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end()); } + SECTION("number (unsigned)") + { + json j_nonarray(json::value_t::number_unsigned); + const json j_nonarray_const(j_nonarray); + CHECK(j_nonarray.find("foo") == j_nonarray.end()); + CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end()); + } + SECTION("number (floating-point)") { json j_nonarray(json::value_t::number_float); @@ -4004,7 +4288,7 @@ TEST_CASE("element access") SECTION("existing element") { for (auto key : - {"integer", "floating", "null", "string", "boolean", "object", "array" + {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array" }) { CHECK(j.count(key) == 1); @@ -4068,6 +4352,14 @@ TEST_CASE("element access") CHECK(j_nonobject_const.count("foo") == 0); } + SECTION("number (unsigned)") + { + json j_nonobject(json::value_t::number_unsigned); + const json j_nonobject_const(j_nonobject); + CHECK(j_nonobject.count("foo") == 0); + CHECK(j_nonobject_const.count("foo") == 0); + } + SECTION("number (floating-point)") { json j_nonobject(json::value_t::number_float); @@ -4143,6 +4435,20 @@ TEST_CASE("element access") } } + SECTION("number (unsigned)") + { + { + json j = 17u; + CHECK(j.front() == j); + CHECK(j.back() == j); + } + { + const json j = 17u; + CHECK(j.front() == j); + CHECK(j.back() == j); + } + } + SECTION("number (floating point)") { { @@ -4222,6 +4528,22 @@ TEST_CASE("element access") } } + SECTION("number (unsigned)") + { + { + json j = 17u; + json::iterator it = j.erase(j.begin()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + { + json j = 17u; + json::const_iterator it = j.erase(j.cbegin()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + } + SECTION("number (floating point)") { { @@ -4283,6 +4605,20 @@ TEST_CASE("element access") } } + SECTION("number (unsigned)") + { + { + json j = 17u; + CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.end()), "iterator out of range"); + } + { + json j = 17u; + CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.cend()), "iterator out of range"); + } + } + SECTION("number (floating point)") { { @@ -4362,6 +4698,22 @@ TEST_CASE("element access") } } + SECTION("number (unsigned)") + { + { + json j = 17u; + json::iterator it = j.erase(j.begin(), j.end()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + { + json j = 17u; + json::const_iterator it = j.erase(j.cbegin(), j.cend()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + } + SECTION("number (floating point)") { { @@ -4435,6 +4787,24 @@ TEST_CASE("element access") } } + SECTION("number (unsigned)") + { + { + json j = 17u; + CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range); + CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.end(), j.end()), "iterators out of range"); + CHECK_THROWS_WITH(j.erase(j.begin(), j.begin()), "iterators out of range"); + } + { + json j = 17u; + CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range); + CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.cend(), j.cend()), "iterators out of range"); + CHECK_THROWS_WITH(j.erase(j.cbegin(), j.cbegin()), "iterators out of range"); + } + } + SECTION("number (floating point)") { { @@ -5065,7 +5435,7 @@ TEST_CASE("iterators") SECTION("object") { - json j = {{"A", 1}, {"B", 2}, {"C", 3}}; + json j = {{"A", 1},{"B", 2},{"C", 3}}; json j_const(j); SECTION("json + begin/end") @@ -5118,138 +5488,342 @@ TEST_CASE("iterators") SECTION("json + cbegin/cend") { - json::const_iterator it_begin = j.cbegin(); - json::const_iterator it_end = j.cend(); - - auto it = it_begin; - CHECK(it != it_end); - CHECK(*it == j["A"]); + json::const_iterator it_begin = j.cbegin(); + json::const_iterator it_end = j.cend(); + + auto it = it_begin; + CHECK(it != it_end); + CHECK(*it == j["A"]); + + it++; + CHECK(it != it_begin); + CHECK(it != it_end); + CHECK(*it == j["B"]); + + ++it; + CHECK(it != it_begin); + CHECK(it != it_end); + CHECK(*it == j["C"]); + + ++it; + CHECK(it != it_begin); + CHECK(it == it_end); + } + + SECTION("const json + cbegin/cend") + { + json::const_iterator it_begin = j_const.cbegin(); + json::const_iterator it_end = j_const.cend(); + + auto it = it_begin; + CHECK(it != it_end); + CHECK(*it == j_const["A"]); + + it++; + CHECK(it != it_begin); + CHECK(it != it_end); + CHECK(*it == j_const["B"]); + + ++it; + CHECK(it != it_begin); + CHECK(it != it_end); + CHECK(*it == j_const["C"]); + + ++it; + CHECK(it != it_begin); + CHECK(it == it_end); + } + + SECTION("json + rbegin/rend") + { + json::reverse_iterator it_begin = j.rbegin(); + json::reverse_iterator it_end = j.rend(); + + auto it = it_begin; + CHECK(it != it_end); + CHECK(*it == j["C"]); + + it++; + CHECK(it != it_begin); + CHECK(it != it_end); + CHECK(*it == j["B"]); + + ++it; + CHECK(it != it_begin); + CHECK(it != it_end); + CHECK(*it == j["A"]); + + ++it; + CHECK(it != it_begin); + CHECK(it == it_end); + } + + SECTION("json + crbegin/crend") + { + json::const_reverse_iterator it_begin = j.crbegin(); + json::const_reverse_iterator it_end = j.crend(); + + auto it = it_begin; + CHECK(it != it_end); + CHECK(*it == j["C"]); + + it++; + CHECK(it != it_begin); + CHECK(it != it_end); + CHECK(*it == j["B"]); + + ++it; + CHECK(it != it_begin); + CHECK(it != it_end); + CHECK(*it == j["A"]); + + ++it; + CHECK(it != it_begin); + CHECK(it == it_end); + } + + SECTION("const json + crbegin/crend") + { + json::const_reverse_iterator it_begin = j_const.crbegin(); + json::const_reverse_iterator it_end = j_const.crend(); + + auto it = it_begin; + CHECK(it != it_end); + CHECK(*it == j["C"]); + + it++; + CHECK(it != it_begin); + CHECK(it != it_end); + CHECK(*it == j["B"]); + + ++it; + CHECK(it != it_begin); + CHECK(it != it_end); + CHECK(*it == j["A"]); + + ++it; + CHECK(it != it_begin); + CHECK(it == it_end); + } + + SECTION("key/value") + { + auto it = j.begin(); + auto cit = j_const.cbegin(); + CHECK(it.key() == "A"); + CHECK(it.value() == json(1)); + CHECK(cit.key() == "A"); + CHECK(cit.value() == json(1)); + } + } + + SECTION("number (integer)") + { + json j = 23; + json j_const(j); + + SECTION("json + begin/end") + { + json::iterator it = j.begin(); + CHECK(it != j.end()); + CHECK(*it == j); + + it++; + CHECK(it != j.begin()); + CHECK(it == j.end()); + + it--; + CHECK(it == j.begin()); + CHECK(it != j.end()); + CHECK(*it == j); + + ++it; + CHECK(it != j.begin()); + CHECK(it == j.end()); + + --it; + CHECK(it == j.begin()); + CHECK(it != j.end()); + CHECK(*it == j); + } + + SECTION("const json + begin/end") + { + json::const_iterator it = j_const.begin(); + CHECK(it != j_const.end()); + CHECK(*it == j_const); + + it++; + CHECK(it != j_const.begin()); + CHECK(it == j_const.end()); + + it--; + CHECK(it == j_const.begin()); + CHECK(it != j_const.end()); + CHECK(*it == j_const); + + ++it; + CHECK(it != j_const.begin()); + CHECK(it == j_const.end()); + + --it; + CHECK(it == j_const.begin()); + CHECK(it != j_const.end()); + CHECK(*it == j_const); + } + + SECTION("json + cbegin/cend") + { + json::const_iterator it = j.cbegin(); + CHECK(it != j.cend()); + CHECK(*it == j); it++; - CHECK(it != it_begin); - CHECK(it != it_end); - CHECK(*it == j["B"]); + CHECK(it != j.cbegin()); + CHECK(it == j.cend()); - ++it; - CHECK(it != it_begin); - CHECK(it != it_end); - CHECK(*it == j["C"]); + it--; + CHECK(it == j.cbegin()); + CHECK(it != j.cend()); + CHECK(*it == j); ++it; - CHECK(it != it_begin); - CHECK(it == it_end); + CHECK(it != j.cbegin()); + CHECK(it == j.cend()); + + --it; + CHECK(it == j.cbegin()); + CHECK(it != j.cend()); + CHECK(*it == j); } SECTION("const json + cbegin/cend") { - json::const_iterator it_begin = j_const.cbegin(); - json::const_iterator it_end = j_const.cend(); - - auto it = it_begin; - CHECK(it != it_end); - CHECK(*it == j_const["A"]); + json::const_iterator it = j_const.cbegin(); + CHECK(it != j_const.cend()); + CHECK(*it == j_const); it++; - CHECK(it != it_begin); - CHECK(it != it_end); - CHECK(*it == j_const["B"]); + CHECK(it != j_const.cbegin()); + CHECK(it == j_const.cend()); - ++it; - CHECK(it != it_begin); - CHECK(it != it_end); - CHECK(*it == j_const["C"]); + it--; + CHECK(it == j_const.cbegin()); + CHECK(it != j_const.cend()); + CHECK(*it == j_const); ++it; - CHECK(it != it_begin); - CHECK(it == it_end); + CHECK(it != j_const.cbegin()); + CHECK(it == j_const.cend()); + + --it; + CHECK(it == j_const.cbegin()); + CHECK(it != j_const.cend()); + CHECK(*it == j_const); } SECTION("json + rbegin/rend") { - json::reverse_iterator it_begin = j.rbegin(); - json::reverse_iterator it_end = j.rend(); - - auto it = it_begin; - CHECK(it != it_end); - CHECK(*it == j["C"]); + json::reverse_iterator it = j.rbegin(); + CHECK(it != j.rend()); + CHECK(*it == j); it++; - CHECK(it != it_begin); - CHECK(it != it_end); - CHECK(*it == j["B"]); + CHECK(it != j.rbegin()); + CHECK(it == j.rend()); - ++it; - CHECK(it != it_begin); - CHECK(it != it_end); - CHECK(*it == j["A"]); + it--; + CHECK(it == j.rbegin()); + CHECK(it != j.rend()); + CHECK(*it == j); ++it; - CHECK(it != it_begin); - CHECK(it == it_end); + CHECK(it != j.rbegin()); + CHECK(it == j.rend()); + + --it; + CHECK(it == j.rbegin()); + CHECK(it != j.rend()); + CHECK(*it == j); } SECTION("json + crbegin/crend") { - json::const_reverse_iterator it_begin = j.crbegin(); - json::const_reverse_iterator it_end = j.crend(); - - auto it = it_begin; - CHECK(it != it_end); - CHECK(*it == j["C"]); + json::const_reverse_iterator it = j.crbegin(); + CHECK(it != j.crend()); + CHECK(*it == j); it++; - CHECK(it != it_begin); - CHECK(it != it_end); - CHECK(*it == j["B"]); + CHECK(it != j.crbegin()); + CHECK(it == j.crend()); - ++it; - CHECK(it != it_begin); - CHECK(it != it_end); - CHECK(*it == j["A"]); + it--; + CHECK(it == j.crbegin()); + CHECK(it != j.crend()); + CHECK(*it == j); ++it; - CHECK(it != it_begin); - CHECK(it == it_end); + CHECK(it != j.crbegin()); + CHECK(it == j.crend()); + + --it; + CHECK(it == j.crbegin()); + CHECK(it != j.crend()); + CHECK(*it == j); } SECTION("const json + crbegin/crend") { - json::const_reverse_iterator it_begin = j_const.crbegin(); - json::const_reverse_iterator it_end = j_const.crend(); - - auto it = it_begin; - CHECK(it != it_end); - CHECK(*it == j["C"]); + json::const_reverse_iterator it = j_const.crbegin(); + CHECK(it != j_const.crend()); + CHECK(*it == j_const); it++; - CHECK(it != it_begin); - CHECK(it != it_end); - CHECK(*it == j["B"]); + CHECK(it != j_const.crbegin()); + CHECK(it == j_const.crend()); - ++it; - CHECK(it != it_begin); - CHECK(it != it_end); - CHECK(*it == j["A"]); + it--; + CHECK(it == j_const.crbegin()); + CHECK(it != j_const.crend()); + CHECK(*it == j_const); ++it; - CHECK(it != it_begin); - CHECK(it == it_end); + CHECK(it != j_const.crbegin()); + CHECK(it == j_const.crend()); + + --it; + CHECK(it == j_const.crbegin()); + CHECK(it != j_const.crend()); + CHECK(*it == j_const); } SECTION("key/value") { auto it = j.begin(); auto cit = j_const.cbegin(); - CHECK(it.key() == "A"); - CHECK(it.value() == json(1)); - CHECK(cit.key() == "A"); - CHECK(cit.value() == json(1)); + CHECK_THROWS_AS(it.key(), std::domain_error); + CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators"); + CHECK(it.value() == json(23)); + CHECK_THROWS_AS(cit.key(), std::domain_error); + CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators"); + CHECK(cit.value() == json(23)); + + auto rit = j.rend(); + auto crit = j.crend(); + CHECK_THROWS_AS(rit.key(), std::domain_error); + CHECK_THROWS_AS(rit.value(), std::out_of_range); + CHECK_THROWS_AS(crit.key(), std::domain_error); + CHECK_THROWS_AS(crit.value(), std::out_of_range); + CHECK_THROWS_WITH(rit.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(rit.value(), "cannot get value"); + CHECK_THROWS_WITH(crit.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(crit.value(), "cannot get value"); } } - SECTION("number (integer)") + SECTION("number (unsigned)") { - json j = 23; + json j = 23u; json j_const(j); SECTION("json + begin/end") @@ -5734,7 +6308,7 @@ TEST_CASE("iterators") SECTION("iterator comparisons") { - json j_values = {nullptr, true, 42, 23.23, {{"one", 1}, {"two", 2}}, {1, 2, 3, 4, 5}, "Hello, world"}; + json j_values = {nullptr, true, 42, 42u, 23.23, {{"one", 1}, {"two", 2}}, {1, 2, 3, 4, 5}, "Hello, world"}; for (json& j : j_values) { @@ -6153,7 +6727,7 @@ TEST_CASE("iterators") SECTION("reverse iterator comparisons") { - json j_values = {nullptr, true, 42, 23.23, {{"one", 1}, {"two", 2}}, {1, 2, 3, 4, 5}, "Hello, world"}; + json j_values = {nullptr, true, 42, 42u, 23.23, {{"one", 1}, {"two", 2}}, {1, 2, 3, 4, 5}, "Hello, world"}; for (json& j : j_values) { @@ -6707,6 +7281,24 @@ TEST_CASE("capacity") } } + SECTION("number (unsigned)") + { + json j = 23u; + json j_const(j); + + SECTION("result of empty") + { + CHECK(j.empty() == false); + CHECK(j_const.empty() == false); + } + + SECTION("definition of empty") + { + CHECK(j.begin() != j.end()); + CHECK(j_const.begin() != j_const.end()); + } + } + SECTION("number (float)") { json j = 23.42; @@ -6892,6 +7484,26 @@ TEST_CASE("capacity") } } + SECTION("number (unsigned)") + { + json j = 23u; + json j_const(j); + + SECTION("result of size") + { + CHECK(j.size() == 1); + CHECK(j_const.size() == 1); + } + + SECTION("definition of size") + { + CHECK(std::distance(j.begin(), j.end()) == j.size()); + CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size()); + CHECK(std::distance(j.rbegin(), j.rend()) == j.size()); + CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size()); + } + } + SECTION("number (float)") { json j = 23.42; @@ -7025,6 +7637,18 @@ TEST_CASE("capacity") } } + SECTION("number (unsigned)") + { + json j = 23u; + json j_const(j); + + SECTION("result of max_size") + { + CHECK(j.max_size() == 1); + CHECK(j_const.max_size() == 1); + } + } + SECTION("number (float)") { json j = 23.42; @@ -7121,6 +7745,14 @@ TEST_CASE("modifiers") CHECK(j == json(json::value_t::number_integer)); } + SECTION("number (unsigned)") + { + json j = 23u; + + j.clear(); + CHECK(j == json(json::value_t::number_integer)); + } + SECTION("number (float)") { json j = 23.42; @@ -7661,6 +8293,7 @@ TEST_CASE("lexicographical comparison operators") json::value_t::null, json::value_t::boolean, json::value_t::number_integer, + json::value_t::number_unsigned, json::value_t::number_float, json::value_t::object, json::value_t::array, @@ -7671,13 +8304,14 @@ TEST_CASE("lexicographical comparison operators") { std::vector> expected = { - {false, true, true, true, true, true, true}, - {false, false, true, true, true, true, true}, - {false, false, false, false, true, true, true}, - {false, false, false, false, true, true, true}, - {false, false, false, false, false, true, true}, - {false, false, false, false, false, false, true}, - {false, false, false, false, false, false, false} + {false, true, true, true, true, true, true, true}, + {false, false, true, true, true, true, true, true}, + {false, false, false, false, false, true, true, true}, + {false, false, false, false, false, true, true, true}, + {false, false, false, false, false, true, true, true}, + {false, false, false, false, false, false, true, true}, + {false, false, false, false, false, false, false, true}, + {false, false, false, false, false, false, false, false} }; for (size_t i = 0; i < j_types.size(); ++i) @@ -7699,6 +8333,7 @@ TEST_CASE("lexicographical comparison operators") { nullptr, nullptr, 17, 42, + 8u, 13u, 3.14159, 23.42, "foo", "bar", true, false, @@ -7710,26 +8345,30 @@ TEST_CASE("lexicographical comparison operators") { std::vector> expected = { - {true, true, false, false, false, false, false, false, false, false, false, false, false, false}, - {true, true, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, true, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, true, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, true, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, true, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, true, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, true, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, true, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, true, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, true, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, true, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, true, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, true} + {true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, + {true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, + {false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false}, + {false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false}, + {false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false}, + {false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false}, + {false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false}, + {false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false}, + {false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false}, + {false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false}, + {false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false}, + {false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false}, + {false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false}, + {false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false}, + {false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false}, + {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true} }; for (size_t i = 0; i < j_values.size(); ++i) { for (size_t j = 0; j < j_values.size(); ++j) { + CAPTURE(i); + CAPTURE(j); // check precomputed values CHECK( (j_values[i] == j_values[j]) == expected[i][j] ); } @@ -7756,6 +8395,8 @@ TEST_CASE("lexicographical comparison operators") { for (size_t j = 0; j < j_values.size(); ++j) { + CAPTURE(i); + CAPTURE(j); // check definition CHECK( (j_values[i] != j_values[j]) == not(j_values[i] == j_values[j]) ); } @@ -7773,26 +8414,30 @@ TEST_CASE("lexicographical comparison operators") { std::vector> expected = { - {false, false, true, true, true, true, true, true, true, true, true, true, true, true}, - {false, false, true, true, true, true, true, true, true, true, true, true, true, true}, - {false, false, false, true, false, true, true, true, false, false, true, true, true, true}, - {false, false, false, false, false, false, true, true, false, false, true, true, true, true}, - {false, false, true, true, false, true, true, true, false, false, true, true, true, true}, - {false, false, false, true, false, false, true, true, false, false, true, true, true, true}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, true, false, false, false, false, false, false, false}, - {false, false, true, true, true, true, true, true, false, false, true, true, true, true}, - {false, false, true, true, true, true, true, true, true, false, true, true, true, true}, - {false, false, false, false, false, false, true, true, false, false, false, true, false, false}, - {false, false, false, false, false, false, true, true, false, false, false, false, false, false}, - {false, false, false, false, false, false, true, true, false, false, true, true, false, false}, - {false, false, false, false, false, false, true, true, false, false, true, true, true, false} + {false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true}, + {false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true}, + {false, false, false, true, false, false, false, true, true, true, false, false, true, true, true, true}, + {false, false, false, false, false, false, false, false, true, true, false, false, true, true, true, true}, + {false, false, true, true, false, true, false, true, true, true, false, false, true, true, true, true}, + {false, false, true, true, false, false, false, true, true, true, false, false, true, true, true, true}, + {false, false, true, true, true, true, false, true, true, true, false, false, true, true, true, true}, + {false, false, false, true, false, false, false, false, true, true, false, false, true, true, true, true}, + {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, + {false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false}, + {false, false, true, true, true, true, true, true, true, true, false, false, true, true, true, true}, + {false, false, true, true, true, true, true, true, true, true, true, false, true, true, true, true}, + {false, false, false, false, false, false, false, false, true, true, false, false, false, true, false, false}, + {false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false}, + {false, false, false, false, false, false, false, false, true, true, false, false, true, true, false, false}, + {false, false, false, false, false, false, false, false, true, true, false, false, true, true, true, false} }; for (size_t i = 0; i < j_values.size(); ++i) { for (size_t j = 0; j < j_values.size(); ++j) { + CAPTURE(i); + CAPTURE(j); // check precomputed values CHECK( (j_values[i] < j_values[j]) == expected[i][j] ); } @@ -7802,6 +8447,7 @@ TEST_CASE("lexicographical comparison operators") json j_discarded(json::value_t::discarded); for (size_t i = 0; i < j_values.size(); ++i) { + CAPTURE(i); CHECK( (j_values[i] < j_discarded) == false); CHECK( (j_discarded < j_values[i]) == false); CHECK( (j_discarded < j_discarded) == false); @@ -7814,6 +8460,8 @@ TEST_CASE("lexicographical comparison operators") { for (size_t j = 0; j < j_values.size(); ++j) { + CAPTURE(i); + CAPTURE(j); // check definition CHECK( (j_values[i] <= j_values[j]) == not(j_values[j] < j_values[i]) ); } @@ -7826,6 +8474,8 @@ TEST_CASE("lexicographical comparison operators") { for (size_t j = 0; j < j_values.size(); ++j) { + CAPTURE(i); + CAPTURE(j); // check definition CHECK( (j_values[i] > j_values[j]) == (j_values[j] < j_values[i]) ); } @@ -7838,6 +8488,8 @@ TEST_CASE("lexicographical comparison operators") { for (size_t j = 0; j < j_values.size(); ++j) { + CAPTURE(i); + CAPTURE(j); // check definition CHECK( (j_values[i] >= j_values[j]) == not(j_values[i] < j_values[j]) ); } @@ -9009,6 +9661,23 @@ TEST_CASE("parser class") // (2**53)-1 CHECK(json::parser("9007199254740991").parse().get() == 9007199254740991); } + + SECTION("over the edge cases") // issue #178 - Integer conversion to unsigned (incorrect handling of 64 bit integers) + { + // While RFC7159, Section 6 specifies a preference for support + // for ranges in range of IEEE 754-2008 binary64 (double precision) + // this does not accommodate 64 bit integers without loss of accuracy. + // As 64 bit integers are now widely used in software, it is desirable + // to expand support to to the full 64 bit (signed and unsigned) range + // i.e. -(2**63) -> (2**64)-1. + + // -(2**63) ** Note: compilers see negative literals as negated positive numbers (hence the -1)) + CHECK(json::parser("-9223372036854775808").parse().get() == -9223372036854775807-1); + // (2**63)-1 + CHECK(json::parser("9223372036854775807").parse().get() == 9223372036854775807); + // (2**64)-1 + CHECK(json::parser("18446744073709551615").parse().get() == 18446744073709551615u); + } } SECTION("floating-point") @@ -9051,12 +9720,12 @@ TEST_CASE("parser class") CHECK_THROWS_WITH(json::parser("01").parse(), "parse error - 0 is not a number"); CHECK_THROWS_WITH(json::parser("--1").parse(), "parse error - unexpected '-'"); - CHECK_THROWS_WITH(json::parser("1.").parse(), "parse error - 1 is not a number"); + CHECK_THROWS_WITH(json::parser("1.").parse(), "parse error - unexpected '.'; expected end of input"); CHECK_THROWS_WITH(json::parser("1E").parse(), "parse error - unexpected 'E'; expected end of input"); CHECK_THROWS_WITH(json::parser("1E-").parse(), "parse error - unexpected 'E'; expected end of input"); - CHECK_THROWS_WITH(json::parser("1.E1").parse(), "parse error - 1 is not a number"); + CHECK_THROWS_WITH(json::parser("1.E1").parse(), "parse error - unexpected '.'; expected end of input"); CHECK_THROWS_WITH(json::parser("-1E").parse(), "parse error - unexpected 'E'; expected end of input"); CHECK_THROWS_WITH(json::parser("-0E#").parse(), @@ -9098,18 +9767,18 @@ TEST_CASE("parser class") CHECK_THROWS_AS(json::parser("1E.").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("1E/").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("1E:").parse(), std::invalid_argument); - CHECK_THROWS_WITH(json::parser("0.").parse(), "parse error - 0 is not a number"); + CHECK_THROWS_WITH(json::parser("0.").parse(), "parse error - unexpected '.'; expected end of input"); CHECK_THROWS_WITH(json::parser("-").parse(), "parse error - unexpected '-'"); CHECK_THROWS_WITH(json::parser("--").parse(), "parse error - unexpected '-'"); CHECK_THROWS_WITH(json::parser("-0.").parse(), - "parse error - -0 is not a number"); + "parse error - unexpected '.'; expected end of input"); CHECK_THROWS_WITH(json::parser("-.").parse(), "parse error - unexpected '-'"); CHECK_THROWS_WITH(json::parser("-:").parse(), "parse error - unexpected '-'"); CHECK_THROWS_WITH(json::parser("0.:").parse(), - "parse error - 0 is not a number"); + "parse error - unexpected '.'; expected end of input"); CHECK_THROWS_WITH(json::parser("e.").parse(), "parse error - unexpected 'e'"); CHECK_THROWS_WITH(json::parser("1e.").parse(), @@ -11082,14 +11751,14 @@ TEST_CASE("compliance tests from nativejson-benchmark") "test/json_roundtrip/roundtrip10.json", "test/json_roundtrip/roundtrip11.json", "test/json_roundtrip/roundtrip12.json", - //"test/json_roundtrip/roundtrip13.json", + "test/json_roundtrip/roundtrip13.json", "test/json_roundtrip/roundtrip14.json", "test/json_roundtrip/roundtrip15.json", "test/json_roundtrip/roundtrip16.json", "test/json_roundtrip/roundtrip17.json", - //"test/json_roundtrip/roundtrip18.json", - //"test/json_roundtrip/roundtrip19.json", - //"test/json_roundtrip/roundtrip20.json", + "test/json_roundtrip/roundtrip18.json", + "test/json_roundtrip/roundtrip19.json", + "test/json_roundtrip/roundtrip20.json", "test/json_roundtrip/roundtrip21.json", "test/json_roundtrip/roundtrip22.json", "test/json_roundtrip/roundtrip23.json", @@ -11407,11 +12076,33 @@ TEST_CASE("regression tests") SECTION("issue #89 - nonstandard integer type") { // create JSON class with nonstandard integer number type - using custom_json = nlohmann::basic_json; + using custom_json = nlohmann::basic_json; custom_json j; j["int_1"] = 1; // we need to cast to int to compile with Catch - the value is int32_t CHECK(static_cast(j["int_1"]) == 1); + + // tests for correct handling of non-standard integers that overflow the type selected by the user + + // unsigned integer object creation - expected to wrap and still be stored as an integer + j = 4294967296U; // 2^32 + CHECK(static_cast(j.type()) == static_cast(custom_json::value_t::number_unsigned)); + CHECK(j.get() == 0); // Wrap + + // unsigned integer parsing - expected to overflow and be stored as a float + j = custom_json::parse("4294967296"); // 2^32 + CHECK(static_cast(j.type()) == static_cast(custom_json::value_t::number_float)); + CHECK(j.get() == 4294967296.0); + + // integer object creation - expected to wrap and still be stored as an integer + j = -2147483649LL; // -2^31-1 + CHECK(static_cast(j.type()) == static_cast(custom_json::value_t::number_integer)); + CHECK(j.get() == 2147483647.0); // Wrap + + // integer parsing - expected to overflow and be stored as a float + j = custom_json::parse("-2147483648"); // -2^31 + CHECK(static_cast(j.type()) == static_cast(custom_json::value_t::number_float)); + CHECK(j.get() == -2147483648.0); } SECTION("issue #93 reverse_iterator operator inheritance problem") @@ -11510,11 +12201,11 @@ TEST_CASE("regression tests") { CHECK(json::parse("\"\\ud80c\\udc60abc\"").get() == u8"\U00013060abc"); } - + SECTION("issue #171 - Cannot index by key of type static constexpr const char*") { json j; - + // Non-const access with key as "char []" char array_key[] = "Key1"; CHECK_NOTHROW(j[array_key] = 1); @@ -11549,7 +12240,7 @@ TEST_CASE("regression tests") // Const access with key as "const char[]" CHECK(j_const[const_array_key] == json(2)); - //Const access with key as "char *" + // Const access with key as "char *" CHECK(j_const[ptr_key] == json(3)); // Const access with key as "const char *" @@ -11594,16 +12285,16 @@ TEST_CASE("regression tests") // create JSON class with nonstandard float number type // float - nlohmann::basic_json j_float = 1.23e25f; + nlohmann::basic_json j_float = 1.23e25f; CHECK(j_float.get() == 1.23e25f); // double - nlohmann::basic_json j_double = 1.23e45; - CHECK(j_double.get() == 1.23e45); + nlohmann::basic_json j_double = 1.23e35f; + CHECK(j_double.get() == 1.23e35f); // long double - nlohmann::basic_json j_long_double = - 1.23e45L; + nlohmann::basic_json j_long_double = 1.23e45L; CHECK(j_long_double.get() == 1.23e45L); } } +