Merge branch 'develop' into feature/allocators
diff --git a/.gitignore b/.gitignore
index d5bd2f7..fd41a2e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,5 @@
android
doc/xml
+
+benchmarks/files/numbers/*.json
diff --git a/.travis.yml b/.travis.yml
index b459f5d..ffe05ec 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -168,3 +168,9 @@
- if [ `which valgrind` ]; then
valgrind --error-exitcode=1 --leak-check=full ./json_unit ;
fi
+ - if [ `which brew` ]; then
+ brew update ;
+ brew tap nlohmann/json ;
+ brew install nlohmann_json --HEAD ;
+ brew test nlohmann_json ;
+ fi
diff --git a/ChangeLog.md b/ChangeLog.md
index e3a7cc0..901e37c 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -5,6 +5,9 @@
[Full Changelog](https://github.com/nlohmann/json/compare/v2.0.1...HEAD)
+- value\(\) does not work with \_json\_pointer types [\#283](https://github.com/nlohmann/json/issues/283)
+- Easy serialization of classes [\#280](https://github.com/nlohmann/json/issues/280)
+
- Build error for std::int64 [\#282](https://github.com/nlohmann/json/issues/282)
- hexify\(\) function emits conversion warning [\#270](https://github.com/nlohmann/json/issues/270)
diff --git a/Makefile b/Makefile
index 1b8b83d..46d176c 100644
--- a/Makefile
+++ b/Makefile
@@ -10,6 +10,7 @@
# clean up
clean:
rm -fr json_unit json_benchmarks fuzz fuzz-testing *.dSYM
+ rm -fr benchmarks/files/numbers/*.json
$(MAKE) clean -Cdoc
@@ -87,6 +88,7 @@
# benchmarks
json_benchmarks: benchmarks/benchmarks.cpp benchmarks/benchpress.hpp benchmarks/cxxopts.hpp src/json.hpp
+ cd benchmarks/files/numbers ; python generate.py
$(CXX) -std=c++11 $(CXXFLAGS) -O3 -flto -I src -I benchmarks $< $(LDFLAGS) -o $@
./json_benchmarks
diff --git a/README.md b/README.md
index a62835c..ed1e807 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@
- **Memory efficiency**. Each JSON object has an overhead of one pointer (the maximal size of a union) and one enumeration element (1 byte). The default generalization uses the following C++ data types: `std::string` for strings, `int64_t`, `uint64_t` or `double` for numbers, `std::map` for objects, `std::vector` for arrays, and `bool` for Booleans. However, you can template the generalized class `basic_json` to your needs.
-- **Speed**. We currently implement the parser as naive [recursive descent parser](http://en.wikipedia.org/wiki/Recursive_descent_parser) with hand coded string handling. It is fast enough, but a [LALR-parser](http://en.wikipedia.org/wiki/LALR_parser) with a decent regular expression processor should be even faster (but would consist of more files which makes the integration harder).
+- **Speed**. We currently implement the parser as naive [recursive descent parser](http://en.wikipedia.org/wiki/Recursive_descent_parser) with hand coded string handling. It is fast enough, but a [LALR-parser](http://en.wikipedia.org/wiki/LALR_parser) may be even faster (but would consist of more files which makes the integration harder).
See the [contribution guidelines](https://github.com/nlohmann/json/blob/master/.github/CONTRIBUTING.md#please-dont) for more information.
diff --git a/benchmarks/benchmarks.cpp b/benchmarks/benchmarks.cpp
index 1f5eb5e..ec6b462 100644
--- a/benchmarks/benchmarks.cpp
+++ b/benchmarks/benchmarks.cpp
@@ -44,6 +44,36 @@
}
})
+BENCHMARK("parse numbers/floats.json", [](benchpress::context* ctx)
+{
+ for (size_t i = 0; i < ctx->num_iterations(); ++i)
+ {
+ std::ifstream input_file("benchmarks/files/numbers/floats.json");
+ nlohmann::json j;
+ j << input_file;
+ }
+})
+
+BENCHMARK("parse numbers/signed_ints.json", [](benchpress::context* ctx)
+{
+ for (size_t i = 0; i < ctx->num_iterations(); ++i)
+ {
+ std::ifstream input_file("benchmarks/files/numbers/signed_ints.json");
+ nlohmann::json j;
+ j << input_file;
+ }
+})
+
+BENCHMARK("parse numbers/unsigned_ints.json", [](benchpress::context* ctx)
+{
+ for (size_t i = 0; i < ctx->num_iterations(); ++i)
+ {
+ std::ifstream input_file("benchmarks/files/numbers/unsigned_ints.json");
+ nlohmann::json j;
+ j << input_file;
+ }
+})
+
BENCHMARK("dump jeopardy.json", [](benchpress::context* ctx)
{
std::ifstream input_file("benchmarks/files/jeopardy/jeopardy.json");
diff --git a/benchmarks/files/numbers/generate.py b/benchmarks/files/numbers/generate.py
new file mode 100755
index 0000000..714ee3a
--- /dev/null
+++ b/benchmarks/files/numbers/generate.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+import json
+import random
+import sys
+
+random.seed(0)
+
+# floats
+result_floats = []
+for x in range(0, 1000000):
+ result_floats.append(random.uniform(-100000000.0, 100000000.0))
+json.dump(result_floats, open("floats.json", "w"), indent=2)
+
+# unsigned integers
+result_uints = []
+for x in range(0, 1000000):
+ result_uints.append(random.randint(0, 18446744073709551615))
+json.dump(result_uints, open("unsigned_ints.json", "w"), indent=2)
+
+# signed integers
+result_sints = []
+for x in range(0, 1000000):
+ result_sints.append(random.randint(-9223372036854775808, 9223372036854775807))
+json.dump(result_sints, open("signed_ints.json", "w"), indent=2)
diff --git a/doc/examples/basic_json__value_ptr.cpp b/doc/examples/basic_json__value_ptr.cpp
new file mode 100644
index 0000000..f45fb8b
--- /dev/null
+++ b/doc/examples/basic_json__value_ptr.cpp
@@ -0,0 +1,29 @@
+#include <json.hpp>
+
+using json = nlohmann::json;
+
+int main()
+{
+ // create a JSON object with different entry types
+ json j =
+ {
+ {"integer", 1},
+ {"floating", 42.23},
+ {"string", "hello world"},
+ {"boolean", true},
+ {"object", {{"key1", 1}, {"key2", 2}}},
+ {"array", {1, 2, 3}}
+ };
+
+ // access existing values
+ int v_integer = j.value("/integer"_json_pointer, 0);
+ double v_floating = j.value("/floating"_json_pointer, 47.11);
+
+ // access nonexisting values and rely on default value
+ std::string v_string = j.value("/nonexisting"_json_pointer, "oops");
+ bool v_boolean = j.value("/nonexisting"_json_pointer, false);
+
+ // output values
+ std::cout << std::boolalpha << v_integer << " " << v_floating
+ << " " << v_string << " " << v_boolean << "\n";
+}
diff --git a/doc/examples/basic_json__value_ptr.link b/doc/examples/basic_json__value_ptr.link
new file mode 100644
index 0000000..2f8fc83
--- /dev/null
+++ b/doc/examples/basic_json__value_ptr.link
@@ -0,0 +1 @@
+<a target="_blank" href="http://melpon.org/wandbox/permlink/K4L4D6nibuGXbjfd"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__value_ptr.output b/doc/examples/basic_json__value_ptr.output
new file mode 100644
index 0000000..dfc40e5
--- /dev/null
+++ b/doc/examples/basic_json__value_ptr.output
@@ -0,0 +1 @@
+1 42.23 oops false
diff --git a/doc/images/callback_events.png b/doc/images/callback_events.png
new file mode 100644
index 0000000..09aa2b3
--- /dev/null
+++ b/doc/images/callback_events.png
Binary files differ
diff --git a/doc/json.gif b/doc/json.gif
index 7edafe0..46f005d 100644
--- a/doc/json.gif
+++ b/doc/json.gif
Binary files differ
diff --git a/src/json.hpp b/src/json.hpp
index b3115b4..d4b6f40 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -32,7 +32,6 @@
#include <algorithm>
#include <array>
#include <cassert>
-#include <cerrno>
#include <ciso646>
#include <cmath>
#include <cstddef>
@@ -44,6 +43,7 @@
#include <iostream>
#include <iterator>
#include <limits>
+#include <locale>
#include <map>
#include <memory>
#include <numeric>
@@ -387,7 +387,7 @@
@tparam ArrayType container type to store arrays (e.g., `std::vector` or
`std::list`)
- @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`)
+ @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`)
#### Default type
@@ -630,15 +630,14 @@
> 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.
+ 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
@@ -726,7 +725,19 @@
This enumeration collects the different JSON types. It is internally used
to distinguish the stored values, and the functions @ref is_null(), @ref
is_object(), @ref is_array(), @ref is_string(), @ref is_boolean(), @ref
- is_number(), and @ref is_discarded() rely on it.
+ is_number() (with @ref is_number_integer(), @ref is_number_unsigned(), and
+ @ref is_number_float()), @ref is_discarded(), @ref is_primitive(), and
+ @ref is_structured() rely on it.
+
+ @note There are three enumeration entries (number_integer,
+ number_unsigned, and number_float), because the library distinguishes
+ these three types for numbers: @ref number_unsigned_t is used for unsigned
+ integers, @ref number_integer_t is used for signed integers, and @ref
+ number_float_t is used for floating-point numbers or to approximate
+ integers which do not fit in the limits of their respective type.
+
+ @sa @ref basic_json(const value_t value_type) -- create a JSON value with
+ the default value for a given type
@since version 1.0.0
*/
@@ -737,7 +748,7 @@
array, ///< array (ordered collection of values)
string, ///< string value
boolean, ///< boolean value
- number_integer, ///< number value (integer)
+ number_integer, ///< number value (signed integer)
number_unsigned, ///< number value (unsigned integer)
number_float, ///< number value (floating-point)
discarded ///< discarded by the the parser callback function
@@ -759,6 +770,7 @@
alloc, 1), deleter);
std::allocator_traits<AllocatorType<T>>::construct(alloc, object.get(),
std::forward<Args>(args)...);
+ assert(object.get() != nullptr);
return object.release();
}
@@ -769,7 +781,24 @@
/*!
@brief a JSON value
- The actual storage for a JSON value of the @ref basic_json class.
+ The actual storage for a JSON value of the @ref basic_json class. This
+ union combines the different storage types for the JSON value types
+ defined in @ref value_t.
+
+ JSON type | value_t type | used type
+ --------- | --------------- | ------------------------
+ object | object | pointer to @ref object_t
+ array | array | pointer to @ref array_t
+ string | string | pointer to @ref string_t
+ boolean | boolean | @ref boolean_t
+ number | number_integer | @ref number_integer_t
+ number | number_unsigned | @ref number_unsigned_t
+ number | number_float | @ref number_float_t
+ null | null | *no value is stored*
+
+ @note Variable-length types (objects, arrays, and strings) are stored as
+ pointers. The size of the union should not exceed 64 bits if the default
+ value types are used.
@since version 1.0.0
*/
@@ -900,6 +929,8 @@
This enumeration lists the parser events that can trigger calling a
callback function of type @ref parser_callback_t during parsing.
+ @image html callback_events.png "Example when certain parse events are triggered"
+
@since version 1.0.0
*/
enum class parse_event_t : uint8_t
@@ -922,12 +953,13 @@
@brief per-element parser callback type
With a parser callback function, the result of parsing a JSON text can be
- influenced. When passed to @ref parse(std::istream&, parser_callback_t) or
- @ref parse(const string_t&, parser_callback_t), it is called on certain
- events (passed as @ref parse_event_t via parameter @a event) with a set
- recursion depth @a depth and context JSON value @a parsed. The return
- value of the callback function is a boolean indicating whether the element
- that emitted the callback shall be kept or not.
+ influenced. When passed to @ref parse(std::istream&, const
+ parser_callback_t) or @ref parse(const string_t&, const parser_callback_t),
+ it is called on certain events (passed as @ref parse_event_t via parameter
+ @a event) with a set recursion depth @a depth and context JSON value
+ @a parsed. The return value of the callback function is a boolean
+ indicating whether the element that emitted the callback shall be kept or
+ not.
We distinguish six scenarios (determined by the event type) in which the
callback function can be called. The following table describes the values
@@ -942,6 +974,8 @@
parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array
parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value
+ @image html callback_events.png "Example when certain parse events are triggered"
+
Discarding a value (i.e., returning `false`) has different effects
depending on the context in which function was called:
@@ -967,7 +1001,9 @@
@since version 1.0.0
*/
- using parser_callback_t = std::function<bool(int depth, parse_event_t event, basic_json& parsed)>;
+ using parser_callback_t = std::function<bool(int depth,
+ parse_event_t event,
+ basic_json& parsed)>;
//////////////////
@@ -1458,8 +1494,8 @@
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.
+ @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
@param[in] allocator the allocator to use to construct objects (optional)
@@ -1531,8 +1567,8 @@
disallows NaN values:
> Numeric values that cannot be represented in the grammar below (such as
> Infinity and NaN) are not permitted.
- In case the parameter @a val is not a number, a JSON null value is
- created instead.
+ In case the parameter @a val is not a number, a JSON null value is created
+ instead.
@complexity Constant.
@@ -1610,21 +1646,21 @@
1. If the list is empty, an empty JSON object value `{}` is created.
2. If the list consists of pairs whose first element is a string, a JSON
- object value is created where the first elements of the pairs are treated
- as keys and the second elements are as values.
+ object value is created where the first elements of the pairs are
+ treated as keys and the second elements are as values.
3. In all other cases, an array is created.
The rules aim to create the best fit between a C++ initializer list and
JSON values. The rationale is as follows:
1. The empty initializer list is written as `{}` which is exactly an empty
- JSON object.
+ JSON object.
2. C++ has now way of describing mapped types other than to list a list of
- pairs. As JSON requires that keys must be of type string, rule 2 is the
- weakest constraint one can pose on initializer lists to interpret them as
- an object.
+ pairs. As JSON requires that keys must be of type string, rule 2 is the
+ weakest constraint one can pose on initializer lists to interpret them
+ as an object.
3. In all other cases, the initializer list could not be interpreted as
- JSON object type, so interpreting it as JSON array type is safe.
+ JSON object type, so interpreting it as JSON array type is safe.
With the rules described above, the following JSON values cannot be
expressed by an initializer list:
@@ -1992,7 +2028,7 @@
@since version 2.0.0
*/
- explicit basic_json(std::istream& i, parser_callback_t cb = nullptr,
+ explicit basic_json(std::istream& i, const parser_callback_t cb = nullptr,
const allocator_type& allocator = {})
: m_allocator(allocator)
{
@@ -2241,6 +2277,12 @@
// fix locale problems
ss.imbue(std::locale(std::locale(), new DecimalSeparator));
+ // 6, 15 or 16 digits of precision allows round-trip IEEE 754
+ // string->float->string, string->double->string or string->long
+ // double->string; to be safe, we read this value from
+ // std::numeric_limits<number_float_t>::digits10
+ ss.precision(std::numeric_limits<double>::digits10);
+
if (indent >= 0)
{
dump(ss, true, static_cast<unsigned int>(indent));
@@ -2890,8 +2932,10 @@
template<typename ReferenceType, typename ThisType>
static ReferenceType get_ref_impl(ThisType& obj)
{
- // delegate the call to get_ptr<>()
+ // helper type
using PointerType = typename std::add_pointer<ReferenceType>::type;
+
+ // delegate the call to get_ptr<>()
auto ptr = obj.template get_ptr<PointerType>();
if (ptr != nullptr)
@@ -3713,8 +3757,8 @@
/*!
@brief access specified object element with default value
- Returns either a copy of an object's element at the specified key @a key or
- a given default value if no element with key @a key exists.
+ Returns either a copy of an object's element at the specified key @a key
+ or a given default value if no element with key @a key exists.
The function is basically equivalent to executing
@code {.cpp}
@@ -3786,7 +3830,7 @@
/*!
@brief overload for a default value of type const char*
- @copydoc basic_json::value()
+ @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const
*/
string_t value(const typename object_t::key_type& key, const char* default_value) const
{
@@ -3794,6 +3838,81 @@
}
/*!
+ @brief access specified object element via JSON Pointer with default value
+
+ Returns either a copy of an object's element at the specified key @a key
+ or a given default value if no element with key @a key exists.
+
+ The function is basically equivalent to executing
+ @code {.cpp}
+ try {
+ return at(ptr);
+ } catch(std::out_of_range) {
+ return default_value;
+ }
+ @endcode
+
+ @note Unlike @ref at(const json_pointer&), this function does not throw
+ if the given key @a key was not found.
+
+ @param[in] ptr a JSON pointer to the element to access
+ @param[in] default_value the value to return if @a ptr found no value
+
+ @tparam ValueType type compatible to JSON values, for instance `int` for
+ JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for
+ JSON arrays. Note the type of the expected value at @a key and the default
+ value @a default_value must be compatible.
+
+ @return copy of the element at key @a key or @a default_value if @a key
+ is not found
+
+ @throw std::domain_error if JSON is not an object; example: `"cannot use
+ value() with null"`
+
+ @complexity Logarithmic in the size of the container.
+
+ @liveexample{The example below shows how object elements can be queried
+ with a default value.,basic_json__value_ptr}
+
+ @sa @ref operator[](const json_pointer&) for unchecked access by reference
+
+ @since version 2.0.2
+ */
+ template <class ValueType, typename
+ std::enable_if<
+ std::is_convertible<basic_json_t, ValueType>::value
+ , int>::type = 0>
+ ValueType value(const json_pointer& ptr, ValueType default_value) const
+ {
+ // at only works for objects
+ if (is_object())
+ {
+ // if pointer resolves a value, return it or use default value
+ try
+ {
+ return ptr.get_checked(this);
+ }
+ catch (std::out_of_range&)
+ {
+ return default_value;
+ }
+ }
+ else
+ {
+ throw std::domain_error("cannot use value() with " + type_name());
+ }
+ }
+
+ /*!
+ @brief overload for a default value of type const char*
+ @copydoc basic_json::value(const json_pointer&, ValueType) const
+ */
+ string_t value(const json_pointer& ptr, const char* default_value) const
+ {
+ return value(ptr, string_t(default_value));
+ }
+
+ /*!
@brief access the first element
Returns a reference to the first element in the container. For a JSON
@@ -4600,6 +4719,10 @@
object | result of function `object_t::empty()`
array | result of function `array_t::empty()`
+ @note This function does not return whether a string stored as JSON value
+ is empty - it returns whether the JSON container itself is empty which is
+ false in the case of a string.
+
@complexity Constant, as long as @ref array_t and @ref object_t satisfy
the Container concept; that is, their `empty()` functions have constant
complexity.
@@ -4629,12 +4752,14 @@
case value_t::array:
{
+ // delegate call to array_t::empty()
assert(m_value.array != nullptr);
return m_value.array->empty();
}
case value_t::object:
{
+ // delegate call to object_t::empty()
assert(m_value.object != nullptr);
return m_value.object->empty();
}
@@ -4663,6 +4788,10 @@
object | result of function object_t::size()
array | result of function array_t::size()
+ @note This function does not return the length of a string stored as JSON
+ value - it returns the number of elements in the JSON value which is 1 in
+ the case of a string.
+
@complexity Constant, as long as @ref array_t and @ref object_t satisfy
the Container concept; that is, their size() functions have constant
complexity.
@@ -4693,12 +4822,14 @@
case value_t::array:
{
+ // delegate call to array_t::size()
assert(m_value.array != nullptr);
return m_value.array->size();
}
case value_t::object:
{
+ // delegate call to object_t::size()
assert(m_value.object != nullptr);
return m_value.object->size();
}
@@ -4753,12 +4884,14 @@
{
case value_t::array:
{
+ // delegate call to array_t::max_size()
assert(m_value.array != nullptr);
return m_value.array->max_size();
}
case value_t::object:
{
+ // delegate call to object_t::max_size()
assert(m_value.object != nullptr);
return m_value.object->max_size();
}
@@ -5814,6 +5947,10 @@
`std::setw(4)` on @a o sets the indentation level to `4` and the
serialization result is the same as calling `dump(4)`.
+ @note During serializaion, the locale and the precision of the output
+ stream @a o are changed. The original values are restored when the
+ function returns.
+
@param[in,out] o stream to serialize to
@param[in] j JSON value to serialize
@@ -5834,14 +5971,23 @@
// reset width to 0 for subsequent calls to this stream
o.width(0);
+
// fix locale problems
- auto old_locale = o.imbue(std::locale(std::locale(), new DecimalSeparator));
+ const auto old_locale = o.imbue(std::locale(std::locale(), new DecimalSeparator));
+ // set precision
+
+ // 6, 15 or 16 digits of precision allows round-trip IEEE 754
+ // string->float->string, string->double->string or string->long
+ // double->string; to be safe, we read this value from
+ // std::numeric_limits<number_float_t>::digits10
+ const auto old_precision = o.precision(std::numeric_limits<double>::digits10);
// do the actual serialization
j.dump(o, pretty_print, static_cast<unsigned int>(indentation));
- // reset locale
+ // reset locale and precision
o.imbue(old_locale);
+ o.precision(old_precision);
return o;
}
@@ -5884,13 +6030,13 @@
@liveexample{The example below demonstrates the `parse()` function with
and without callback function.,parse__string__parser_callback_t}
- @sa @ref parse(std::istream&, parser_callback_t) for a version that reads
- from an input stream
+ @sa @ref parse(std::istream&, const parser_callback_t) for a version that
+ reads from an input stream
@since version 1.0.0
*/
static basic_json parse(const string_t& s,
- parser_callback_t cb = nullptr,
+ const parser_callback_t cb = nullptr,
const allocator_type& allocator = {})
{
return parser(s, cb, allocator).parse();
@@ -5916,23 +6062,23 @@
@liveexample{The example below demonstrates the `parse()` function with
and without callback function.,parse__istream__parser_callback_t}
- @sa @ref parse(const string_t&, parser_callback_t) for a version that
- reads from a string
+ @sa @ref parse(const string_t&, const parser_callback_t) for a version
+ that reads from a string
@since version 1.0.0
*/
static basic_json parse(std::istream& i,
- parser_callback_t cb = nullptr,
+ const parser_callback_t cb = nullptr,
const allocator_type& allocator = {})
{
return parser(i, cb, allocator).parse();
}
/*!
- @copydoc parse(std::istream&, parser_callback_t)
+ @copydoc parse(std::istream&, const parser_callback_t)
*/
static basic_json parse(std::istream&& i,
- parser_callback_t cb = nullptr,
+ const parser_callback_t cb = nullptr,
const allocator_type& allocator = {})
{
return parser(i, cb, allocator).parse();
@@ -5956,8 +6102,8 @@
@liveexample{The example below shows how a JSON value is constructed by
reading a serialization from a stream.,operator_deserialize}
- @sa parse(std::istream&, parser_callback_t) for a variant with a parser
- callback function to filter values while parsing
+ @sa parse(std::istream&, const parser_callback_t) for a variant with a
+ parser callback function to filter values while parsing
@since version 1.0.0
*/
@@ -5985,7 +6131,18 @@
// convenience functions //
///////////////////////////
- /// return the type as string
+ /*!
+ @brief return the type as string
+
+ Returns the type name as string to be used in error messages - usually to
+ indicate that a function was called on a wrong JSON type.
+
+ @return basically a string representation of a the @ref m_type member
+
+ @complexity Constant.
+
+ @since version 1.0.0
+ */
std::string type_name() const
{
switch (m_type)
@@ -6313,13 +6470,7 @@
}
else
{
- // Otherwise 6, 15 or 16 digits of precision allows
- // round-trip IEEE 754 string->float->string,
- // string->double->string or string->long
- // double->string; to be safe, we read this value from
- // std::numeric_limits<number_float_t>::digits10
- o << std::setprecision(std::numeric_limits<double>::digits10)
- << m_value.number_float;
+ o << m_value.number_float;
}
return;
}
@@ -7367,7 +7518,7 @@
explicit lexer(const string_t& s) noexcept
: m_stream(nullptr), m_buffer(s)
{
- m_content = reinterpret_cast<const lexer_char_t*>(s.c_str());
+ m_content = reinterpret_cast<const lexer_char_t*>(m_buffer.c_str());
assert(m_content != nullptr);
m_start = m_cursor = m_content;
m_limit = m_content + s.size();
@@ -7378,7 +7529,7 @@
: m_stream(s), m_buffer()
{
assert(m_stream != nullptr);
- getline(*m_stream, m_buffer);
+ std::getline(*m_stream, m_buffer);
m_content = reinterpret_cast<const lexer_char_t*>(m_buffer.c_str());
assert(m_content != nullptr);
m_start = m_cursor = m_content;
@@ -7393,24 +7544,32 @@
lexer operator=(const lexer&) = delete;
/*!
- @brief create a string from a Unicode code point
+ @brief create a string from one or two Unicode code points
+
+ There are two cases: (1) @a codepoint1 is in the Basic Multilingual
+ Plane (U+0000 through U+FFFF) and @a codepoint2 is 0, or (2)
+ @a codepoint1 and @a codepoint2 are a UTF-16 surrogate pair to
+ represent a code point above U+FFFF.
@param[in] codepoint1 the code point (can be high surrogate)
@param[in] codepoint2 the code point (can be low surrogate or 0)
- @return string representation of the code point
+ @return string representation of the code point; the length of the
+ result string is between 1 and 4 characters.
@throw std::out_of_range if code point is > 0x10ffff; example: `"code
points above 0x10FFFF are invalid"`
@throw std::invalid_argument if the low surrogate is invalid; example:
`""missing or wrong low surrogate""`
+ @complexity Constant.
+
@see <http://en.wikipedia.org/wiki/UTF-8#Sample_code>
*/
static string_t to_unicode(const std::size_t codepoint1,
const std::size_t codepoint2 = 0)
{
- // calculate the codepoint from the given code points
+ // calculate the code point from the given code points
std::size_t codepoint = codepoint1;
// check if codepoint1 is a high surrogate
@@ -7472,7 +7631,7 @@
}
/// return name of values of type token_type (only used for errors)
- static std::string token_type_name(token_type t)
+ static std::string token_type_name(const token_type t)
{
switch (t)
{
@@ -7521,6 +7680,17 @@
function consists of a large block of code with `goto` jumps.
@return the class of the next token read from the buffer
+
+ @complexity Linear in the length of the input.\n
+
+ Proposition: The loop below will always terminate for finite input.\n
+
+ Proof (by contradiction): Assume a finite input. To loop forever, the
+ loop must never hit code with a `break` statement. The only code
+ snippets without a `break` statement are the continue statements for
+ whitespace and byte-order-marks. To loop forever, the input must be an
+ infinite sequence of whitespace or byte-order-marks. This contradicts
+ the assumption of finite input, q.e.d.
*/
token_type scan() noexcept
{
@@ -7541,8 +7711,8 @@
{
0, 0, 0, 0, 0, 0, 0, 0,
0, 32, 32, 0, 0, 32, 0, 0,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
160, 128, 0, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128,
192, 192, 192, 192, 192, 192, 192, 192,
@@ -7721,7 +7891,7 @@
basic_json_parser_9:
yyaccept = 0;
yych = *(m_marker = ++m_cursor);
- if (yych <= 0x0F)
+ if (yych <= 0x1F)
{
goto basic_json_parser_5;
}
@@ -7879,7 +8049,7 @@
{
goto basic_json_parser_31;
}
- if (yych <= 0x0F)
+ if (yych <= 0x1F)
{
goto basic_json_parser_33;
}
@@ -8356,12 +8526,49 @@
of the construction of the values.
2. Unescaped characters are copied as is.
+ @pre `m_cursor - m_start >= 2`, meaning the length of the last token
+ is at least 2 bytes which is trivially true for any string (which
+ consists of at least two quotes).
+
+ " c1 c2 c3 ... "
+ ^ ^
+ m_start m_cursor
+
+ @complexity Linear in the length of the string.\n
+
+ Lemma: The loop body will always terminate.\n
+
+ Proof (by contradiction): Assume the loop body does not terminate. As
+ the loop body does not contain another loop, one of the called
+ functions must never return. The called functions are `std::strtoul`
+ and to_unicode. Neither function can loop forever, so the loop body
+ will never loop forever which contradicts the assumption that the loop
+ body does not terminate, q.e.d.\n
+
+ Lemma: The loop condition for the for loop is eventually false.\n
+
+ Proof (by contradiction): Assume the loop does not terminate. Due to
+ the above lemma, this can only be due to a tautological loop
+ condition; that is, the loop condition i < m_cursor - 1 must always be
+ true. Let x be the change of i for any loop iteration. Then
+ m_start + 1 + x < m_cursor - 1 must hold to loop indefinitely. This
+ can be rephrased to m_cursor - m_start - 2 > x. With the
+ precondition, we x <= 0, meaning that the loop condition holds
+ indefinitly if i is always decreased. However, observe that the value
+ of i is strictly increasing with each iteration, as it is incremented
+ by 1 in the iteration expression and never decremented inside the loop
+ body. Hence, the loop condition will eventually be false which
+ contradicts the assumption that the loop condition is a tautology,
+ q.e.d.
+
@return string value of current token without opening and closing
quotes
@throw std::out_of_range if to_unicode fails
*/
string_t get_string() const
{
+ assert(m_cursor - m_start >= 2);
+
string_t result;
result.reserve(static_cast<size_t>(m_cursor - m_start - 2));
@@ -8477,11 +8684,6 @@
the number
@return the floating point number
-
- @bug This function uses `std::strtof`, `std::strtod`, or `std::strtold`
- which use the current C locale to determine which character is used as
- decimal point character. This may yield to parse errors if the locale
- does not used `.`.
*/
long double str_to_float_t(long double* /* type */, char** endptr) const
{
@@ -8662,7 +8864,7 @@
{
public:
/// constructor for strings
- parser(const string_t& s, parser_callback_t cb = nullptr,
+ parser(const string_t& s, const parser_callback_t cb = nullptr,
const allocator_type& allocator = {}) noexcept
: callback(cb), m_lexer(s), m_allocator(allocator)
{
@@ -8671,7 +8873,7 @@
}
/// a parser reading from an input stream
- parser(std::istream& _is, parser_callback_t cb = nullptr,
+ parser(std::istream& _is, const parser_callback_t cb = nullptr,
const allocator_type& allocator = {}) noexcept
: callback(cb), m_lexer(&_is), m_allocator(allocator)
{
@@ -8920,7 +9122,7 @@
/// current level of recursion
int depth = 0;
/// callback function
- parser_callback_t callback;
+ const parser_callback_t callback = nullptr;
/// the type of the last read token
typename lexer::token_type last_token = lexer::token_type::uninitialized;
/// the lexer
@@ -9038,6 +9240,8 @@
/*!
@brief create and return a reference to the pointed to value
+
+ @complexity Linear in the number of reference tokens.
*/
reference get_and_create(reference j) const
{
@@ -9359,7 +9563,7 @@
@param[in,out] s the string to manipulate
@param[in] f the substring to replace with @a t
- @param[out] t the string to replace @a f
+ @param[in] t the string to replace @a f
@return The string @a s where all occurrences of @a f are replaced
with @a t.
@@ -9475,6 +9679,7 @@
basic_json result;
// iterate the JSON object values
+ assert(value.m_value.object != nullptr);
for (const auto& element : *value.m_value.object)
{
if (not element.second.is_primitive())
@@ -10215,7 +10420,7 @@
@brief user-defined string literal for JSON values
This operator implements a user-defined string literal for JSON objects. It
-can be used by adding \p "_json" to a string literal and returns a JSON object
+can be used by adding `"_json"` to a string literal and returns a JSON object
if no parse error occurred.
@param[in] s a string representation of a JSON object
@@ -10231,6 +10436,13 @@
/*!
@brief user-defined string literal for JSON pointer
+This operator implements a user-defined string literal for JSON Pointers. It
+can be used by adding `"_json"` to a string literal and returns a JSON pointer
+object if no parse error occurred.
+
+@param[in] s a string representation of a JSON Pointer
+@return a JSON pointer object
+
@since version 2.0.0
*/
inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t)
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 03c22d0..e0cb669 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -32,7 +32,6 @@
#include <algorithm>
#include <array>
#include <cassert>
-#include <cerrno>
#include <ciso646>
#include <cmath>
#include <cstddef>
@@ -44,6 +43,7 @@
#include <iostream>
#include <iterator>
#include <limits>
+#include <locale>
#include <map>
#include <memory>
#include <numeric>
@@ -387,7 +387,7 @@
@tparam ArrayType container type to store arrays (e.g., `std::vector` or
`std::list`)
- @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`)
+ @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`)
#### Default type
@@ -630,15 +630,14 @@
> 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.
+ 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
@@ -726,7 +725,19 @@
This enumeration collects the different JSON types. It is internally used
to distinguish the stored values, and the functions @ref is_null(), @ref
is_object(), @ref is_array(), @ref is_string(), @ref is_boolean(), @ref
- is_number(), and @ref is_discarded() rely on it.
+ is_number() (with @ref is_number_integer(), @ref is_number_unsigned(), and
+ @ref is_number_float()), @ref is_discarded(), @ref is_primitive(), and
+ @ref is_structured() rely on it.
+
+ @note There are three enumeration entries (number_integer,
+ number_unsigned, and number_float), because the library distinguishes
+ these three types for numbers: @ref number_unsigned_t is used for unsigned
+ integers, @ref number_integer_t is used for signed integers, and @ref
+ number_float_t is used for floating-point numbers or to approximate
+ integers which do not fit in the limits of their respective type.
+
+ @sa @ref basic_json(const value_t value_type) -- create a JSON value with
+ the default value for a given type
@since version 1.0.0
*/
@@ -737,7 +748,7 @@
array, ///< array (ordered collection of values)
string, ///< string value
boolean, ///< boolean value
- number_integer, ///< number value (integer)
+ number_integer, ///< number value (signed integer)
number_unsigned, ///< number value (unsigned integer)
number_float, ///< number value (floating-point)
discarded ///< discarded by the the parser callback function
@@ -759,6 +770,7 @@
alloc, 1), deleter);
std::allocator_traits<AllocatorType<T>>::construct(alloc, object.get(),
std::forward<Args>(args)...);
+ assert(object.get() != nullptr);
return object.release();
}
@@ -769,7 +781,24 @@
/*!
@brief a JSON value
- The actual storage for a JSON value of the @ref basic_json class.
+ The actual storage for a JSON value of the @ref basic_json class. This
+ union combines the different storage types for the JSON value types
+ defined in @ref value_t.
+
+ JSON type | value_t type | used type
+ --------- | --------------- | ------------------------
+ object | object | pointer to @ref object_t
+ array | array | pointer to @ref array_t
+ string | string | pointer to @ref string_t
+ boolean | boolean | @ref boolean_t
+ number | number_integer | @ref number_integer_t
+ number | number_unsigned | @ref number_unsigned_t
+ number | number_float | @ref number_float_t
+ null | null | *no value is stored*
+
+ @note Variable-length types (objects, arrays, and strings) are stored as
+ pointers. The size of the union should not exceed 64 bits if the default
+ value types are used.
@since version 1.0.0
*/
@@ -900,6 +929,8 @@
This enumeration lists the parser events that can trigger calling a
callback function of type @ref parser_callback_t during parsing.
+ @image html callback_events.png "Example when certain parse events are triggered"
+
@since version 1.0.0
*/
enum class parse_event_t : uint8_t
@@ -922,12 +953,13 @@
@brief per-element parser callback type
With a parser callback function, the result of parsing a JSON text can be
- influenced. When passed to @ref parse(std::istream&, parser_callback_t) or
- @ref parse(const string_t&, parser_callback_t), it is called on certain
- events (passed as @ref parse_event_t via parameter @a event) with a set
- recursion depth @a depth and context JSON value @a parsed. The return
- value of the callback function is a boolean indicating whether the element
- that emitted the callback shall be kept or not.
+ influenced. When passed to @ref parse(std::istream&, const
+ parser_callback_t) or @ref parse(const string_t&, const parser_callback_t),
+ it is called on certain events (passed as @ref parse_event_t via parameter
+ @a event) with a set recursion depth @a depth and context JSON value
+ @a parsed. The return value of the callback function is a boolean
+ indicating whether the element that emitted the callback shall be kept or
+ not.
We distinguish six scenarios (determined by the event type) in which the
callback function can be called. The following table describes the values
@@ -942,6 +974,8 @@
parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array
parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value
+ @image html callback_events.png "Example when certain parse events are triggered"
+
Discarding a value (i.e., returning `false`) has different effects
depending on the context in which function was called:
@@ -967,7 +1001,9 @@
@since version 1.0.0
*/
- using parser_callback_t = std::function<bool(int depth, parse_event_t event, basic_json& parsed)>;
+ using parser_callback_t = std::function<bool(int depth,
+ parse_event_t event,
+ basic_json& parsed)>;
//////////////////
@@ -1458,8 +1494,8 @@
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.
+ @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
@param[in] allocator the allocator to use to construct objects (optional)
@@ -1531,8 +1567,8 @@
disallows NaN values:
> Numeric values that cannot be represented in the grammar below (such as
> Infinity and NaN) are not permitted.
- In case the parameter @a val is not a number, a JSON null value is
- created instead.
+ In case the parameter @a val is not a number, a JSON null value is created
+ instead.
@complexity Constant.
@@ -1610,21 +1646,21 @@
1. If the list is empty, an empty JSON object value `{}` is created.
2. If the list consists of pairs whose first element is a string, a JSON
- object value is created where the first elements of the pairs are treated
- as keys and the second elements are as values.
+ object value is created where the first elements of the pairs are
+ treated as keys and the second elements are as values.
3. In all other cases, an array is created.
The rules aim to create the best fit between a C++ initializer list and
JSON values. The rationale is as follows:
1. The empty initializer list is written as `{}` which is exactly an empty
- JSON object.
+ JSON object.
2. C++ has now way of describing mapped types other than to list a list of
- pairs. As JSON requires that keys must be of type string, rule 2 is the
- weakest constraint one can pose on initializer lists to interpret them as
- an object.
+ pairs. As JSON requires that keys must be of type string, rule 2 is the
+ weakest constraint one can pose on initializer lists to interpret them
+ as an object.
3. In all other cases, the initializer list could not be interpreted as
- JSON object type, so interpreting it as JSON array type is safe.
+ JSON object type, so interpreting it as JSON array type is safe.
With the rules described above, the following JSON values cannot be
expressed by an initializer list:
@@ -1992,7 +2028,7 @@
@since version 2.0.0
*/
- explicit basic_json(std::istream& i, parser_callback_t cb = nullptr,
+ explicit basic_json(std::istream& i, const parser_callback_t cb = nullptr,
const allocator_type& allocator = {})
: m_allocator(allocator)
{
@@ -2241,6 +2277,12 @@
// fix locale problems
ss.imbue(std::locale(std::locale(), new DecimalSeparator));
+ // 6, 15 or 16 digits of precision allows round-trip IEEE 754
+ // string->float->string, string->double->string or string->long
+ // double->string; to be safe, we read this value from
+ // std::numeric_limits<number_float_t>::digits10
+ ss.precision(std::numeric_limits<double>::digits10);
+
if (indent >= 0)
{
dump(ss, true, static_cast<unsigned int>(indent));
@@ -2890,8 +2932,10 @@
template<typename ReferenceType, typename ThisType>
static ReferenceType get_ref_impl(ThisType& obj)
{
- // delegate the call to get_ptr<>()
+ // helper type
using PointerType = typename std::add_pointer<ReferenceType>::type;
+
+ // delegate the call to get_ptr<>()
auto ptr = obj.template get_ptr<PointerType>();
if (ptr != nullptr)
@@ -3713,8 +3757,8 @@
/*!
@brief access specified object element with default value
- Returns either a copy of an object's element at the specified key @a key or
- a given default value if no element with key @a key exists.
+ Returns either a copy of an object's element at the specified key @a key
+ or a given default value if no element with key @a key exists.
The function is basically equivalent to executing
@code {.cpp}
@@ -3786,7 +3830,7 @@
/*!
@brief overload for a default value of type const char*
- @copydoc basic_json::value()
+ @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const
*/
string_t value(const typename object_t::key_type& key, const char* default_value) const
{
@@ -3794,6 +3838,81 @@
}
/*!
+ @brief access specified object element via JSON Pointer with default value
+
+ Returns either a copy of an object's element at the specified key @a key
+ or a given default value if no element with key @a key exists.
+
+ The function is basically equivalent to executing
+ @code {.cpp}
+ try {
+ return at(ptr);
+ } catch(std::out_of_range) {
+ return default_value;
+ }
+ @endcode
+
+ @note Unlike @ref at(const json_pointer&), this function does not throw
+ if the given key @a key was not found.
+
+ @param[in] ptr a JSON pointer to the element to access
+ @param[in] default_value the value to return if @a ptr found no value
+
+ @tparam ValueType type compatible to JSON values, for instance `int` for
+ JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for
+ JSON arrays. Note the type of the expected value at @a key and the default
+ value @a default_value must be compatible.
+
+ @return copy of the element at key @a key or @a default_value if @a key
+ is not found
+
+ @throw std::domain_error if JSON is not an object; example: `"cannot use
+ value() with null"`
+
+ @complexity Logarithmic in the size of the container.
+
+ @liveexample{The example below shows how object elements can be queried
+ with a default value.,basic_json__value_ptr}
+
+ @sa @ref operator[](const json_pointer&) for unchecked access by reference
+
+ @since version 2.0.2
+ */
+ template <class ValueType, typename
+ std::enable_if<
+ std::is_convertible<basic_json_t, ValueType>::value
+ , int>::type = 0>
+ ValueType value(const json_pointer& ptr, ValueType default_value) const
+ {
+ // at only works for objects
+ if (is_object())
+ {
+ // if pointer resolves a value, return it or use default value
+ try
+ {
+ return ptr.get_checked(this);
+ }
+ catch (std::out_of_range&)
+ {
+ return default_value;
+ }
+ }
+ else
+ {
+ throw std::domain_error("cannot use value() with " + type_name());
+ }
+ }
+
+ /*!
+ @brief overload for a default value of type const char*
+ @copydoc basic_json::value(const json_pointer&, ValueType) const
+ */
+ string_t value(const json_pointer& ptr, const char* default_value) const
+ {
+ return value(ptr, string_t(default_value));
+ }
+
+ /*!
@brief access the first element
Returns a reference to the first element in the container. For a JSON
@@ -4600,6 +4719,10 @@
object | result of function `object_t::empty()`
array | result of function `array_t::empty()`
+ @note This function does not return whether a string stored as JSON value
+ is empty - it returns whether the JSON container itself is empty which is
+ false in the case of a string.
+
@complexity Constant, as long as @ref array_t and @ref object_t satisfy
the Container concept; that is, their `empty()` functions have constant
complexity.
@@ -4629,12 +4752,14 @@
case value_t::array:
{
+ // delegate call to array_t::empty()
assert(m_value.array != nullptr);
return m_value.array->empty();
}
case value_t::object:
{
+ // delegate call to object_t::empty()
assert(m_value.object != nullptr);
return m_value.object->empty();
}
@@ -4663,6 +4788,10 @@
object | result of function object_t::size()
array | result of function array_t::size()
+ @note This function does not return the length of a string stored as JSON
+ value - it returns the number of elements in the JSON value which is 1 in
+ the case of a string.
+
@complexity Constant, as long as @ref array_t and @ref object_t satisfy
the Container concept; that is, their size() functions have constant
complexity.
@@ -4693,12 +4822,14 @@
case value_t::array:
{
+ // delegate call to array_t::size()
assert(m_value.array != nullptr);
return m_value.array->size();
}
case value_t::object:
{
+ // delegate call to object_t::size()
assert(m_value.object != nullptr);
return m_value.object->size();
}
@@ -4753,12 +4884,14 @@
{
case value_t::array:
{
+ // delegate call to array_t::max_size()
assert(m_value.array != nullptr);
return m_value.array->max_size();
}
case value_t::object:
{
+ // delegate call to object_t::max_size()
assert(m_value.object != nullptr);
return m_value.object->max_size();
}
@@ -5814,6 +5947,10 @@
`std::setw(4)` on @a o sets the indentation level to `4` and the
serialization result is the same as calling `dump(4)`.
+ @note During serializaion, the locale and the precision of the output
+ stream @a o are changed. The original values are restored when the
+ function returns.
+
@param[in,out] o stream to serialize to
@param[in] j JSON value to serialize
@@ -5834,14 +5971,23 @@
// reset width to 0 for subsequent calls to this stream
o.width(0);
+
// fix locale problems
- auto old_locale = o.imbue(std::locale(std::locale(), new DecimalSeparator));
+ const auto old_locale = o.imbue(std::locale(std::locale(), new DecimalSeparator));
+ // set precision
+
+ // 6, 15 or 16 digits of precision allows round-trip IEEE 754
+ // string->float->string, string->double->string or string->long
+ // double->string; to be safe, we read this value from
+ // std::numeric_limits<number_float_t>::digits10
+ const auto old_precision = o.precision(std::numeric_limits<double>::digits10);
// do the actual serialization
j.dump(o, pretty_print, static_cast<unsigned int>(indentation));
- // reset locale
+ // reset locale and precision
o.imbue(old_locale);
+ o.precision(old_precision);
return o;
}
@@ -5884,13 +6030,13 @@
@liveexample{The example below demonstrates the `parse()` function with
and without callback function.,parse__string__parser_callback_t}
- @sa @ref parse(std::istream&, parser_callback_t) for a version that reads
- from an input stream
+ @sa @ref parse(std::istream&, const parser_callback_t) for a version that
+ reads from an input stream
@since version 1.0.0
*/
static basic_json parse(const string_t& s,
- parser_callback_t cb = nullptr,
+ const parser_callback_t cb = nullptr,
const allocator_type& allocator = {})
{
return parser(s, cb, allocator).parse();
@@ -5916,23 +6062,23 @@
@liveexample{The example below demonstrates the `parse()` function with
and without callback function.,parse__istream__parser_callback_t}
- @sa @ref parse(const string_t&, parser_callback_t) for a version that
- reads from a string
+ @sa @ref parse(const string_t&, const parser_callback_t) for a version
+ that reads from a string
@since version 1.0.0
*/
static basic_json parse(std::istream& i,
- parser_callback_t cb = nullptr,
+ const parser_callback_t cb = nullptr,
const allocator_type& allocator = {})
{
return parser(i, cb, allocator).parse();
}
/*!
- @copydoc parse(std::istream&, parser_callback_t)
+ @copydoc parse(std::istream&, const parser_callback_t)
*/
static basic_json parse(std::istream&& i,
- parser_callback_t cb = nullptr,
+ const parser_callback_t cb = nullptr,
const allocator_type& allocator = {})
{
return parser(i, cb, allocator).parse();
@@ -5956,8 +6102,8 @@
@liveexample{The example below shows how a JSON value is constructed by
reading a serialization from a stream.,operator_deserialize}
- @sa parse(std::istream&, parser_callback_t) for a variant with a parser
- callback function to filter values while parsing
+ @sa parse(std::istream&, const parser_callback_t) for a variant with a
+ parser callback function to filter values while parsing
@since version 1.0.0
*/
@@ -5985,7 +6131,18 @@
// convenience functions //
///////////////////////////
- /// return the type as string
+ /*!
+ @brief return the type as string
+
+ Returns the type name as string to be used in error messages - usually to
+ indicate that a function was called on a wrong JSON type.
+
+ @return basically a string representation of a the @ref m_type member
+
+ @complexity Constant.
+
+ @since version 1.0.0
+ */
std::string type_name() const
{
switch (m_type)
@@ -6313,13 +6470,7 @@
}
else
{
- // Otherwise 6, 15 or 16 digits of precision allows
- // round-trip IEEE 754 string->float->string,
- // string->double->string or string->long
- // double->string; to be safe, we read this value from
- // std::numeric_limits<number_float_t>::digits10
- o << std::setprecision(std::numeric_limits<double>::digits10)
- << m_value.number_float;
+ o << m_value.number_float;
}
return;
}
@@ -7367,7 +7518,7 @@
explicit lexer(const string_t& s) noexcept
: m_stream(nullptr), m_buffer(s)
{
- m_content = reinterpret_cast<const lexer_char_t*>(s.c_str());
+ m_content = reinterpret_cast<const lexer_char_t*>(m_buffer.c_str());
assert(m_content != nullptr);
m_start = m_cursor = m_content;
m_limit = m_content + s.size();
@@ -7378,7 +7529,7 @@
: m_stream(s), m_buffer()
{
assert(m_stream != nullptr);
- getline(*m_stream, m_buffer);
+ std::getline(*m_stream, m_buffer);
m_content = reinterpret_cast<const lexer_char_t*>(m_buffer.c_str());
assert(m_content != nullptr);
m_start = m_cursor = m_content;
@@ -7393,24 +7544,32 @@
lexer operator=(const lexer&) = delete;
/*!
- @brief create a string from a Unicode code point
+ @brief create a string from one or two Unicode code points
+
+ There are two cases: (1) @a codepoint1 is in the Basic Multilingual
+ Plane (U+0000 through U+FFFF) and @a codepoint2 is 0, or (2)
+ @a codepoint1 and @a codepoint2 are a UTF-16 surrogate pair to
+ represent a code point above U+FFFF.
@param[in] codepoint1 the code point (can be high surrogate)
@param[in] codepoint2 the code point (can be low surrogate or 0)
- @return string representation of the code point
+ @return string representation of the code point; the length of the
+ result string is between 1 and 4 characters.
@throw std::out_of_range if code point is > 0x10ffff; example: `"code
points above 0x10FFFF are invalid"`
@throw std::invalid_argument if the low surrogate is invalid; example:
`""missing or wrong low surrogate""`
+ @complexity Constant.
+
@see <http://en.wikipedia.org/wiki/UTF-8#Sample_code>
*/
static string_t to_unicode(const std::size_t codepoint1,
const std::size_t codepoint2 = 0)
{
- // calculate the codepoint from the given code points
+ // calculate the code point from the given code points
std::size_t codepoint = codepoint1;
// check if codepoint1 is a high surrogate
@@ -7472,7 +7631,7 @@
}
/// return name of values of type token_type (only used for errors)
- static std::string token_type_name(token_type t)
+ static std::string token_type_name(const token_type t)
{
switch (t)
{
@@ -7521,6 +7680,17 @@
function consists of a large block of code with `goto` jumps.
@return the class of the next token read from the buffer
+
+ @complexity Linear in the length of the input.\n
+
+ Proposition: The loop below will always terminate for finite input.\n
+
+ Proof (by contradiction): Assume a finite input. To loop forever, the
+ loop must never hit code with a `break` statement. The only code
+ snippets without a `break` statement are the continue statements for
+ whitespace and byte-order-marks. To loop forever, the input must be an
+ infinite sequence of whitespace or byte-order-marks. This contradicts
+ the assumption of finite input, q.e.d.
*/
token_type scan() noexcept
{
@@ -7566,32 +7736,32 @@
"false" { last_token_type = token_type::literal_false; break; }
// number
- decimal_point = [.];
+ decimal_point = ".";
digit = [0-9];
digit_1_9 = [1-9];
- e = [eE];
- minus = [-];
- plus = [+];
- zero = [0];
- exp = e (minus|plus)? digit+;
+ e = "e" | "E";
+ minus = "-";
+ plus = "+";
+ zero = "0";
+ exp = e (minus | plus)? digit+;
frac = decimal_point digit+;
- int = (zero|digit_1_9 digit*);
+ int = (zero | digit_1_9 digit*);
number = minus? int frac? exp?;
number { last_token_type = token_type::value_number; break; }
// string
- quotation_mark = ["];
- escape = [\\];
- unescaped = [^"\\\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F];
- single_escaped = ["\\/bfnrt];
- unicode_escaped = [u][0-9a-fA-F]{4};
+ quotation_mark = "\"";
+ escape = "\\";
+ unescaped = [^"\\\x00-\x1f];
+ single_escaped = "\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t";
+ unicode_escaped = "u" [0-9a-fA-F]{4};
escaped = escape (single_escaped | unicode_escaped);
char = unescaped | escaped;
string = quotation_mark char* quotation_mark;
string { last_token_type = token_type::value_string; break; }
// end of file
- '\000' { last_token_type = token_type::end_of_input; break; }
+ "\000" { last_token_type = token_type::end_of_input; break; }
// anything else is an error
. { last_token_type = token_type::parse_error; break; }
@@ -7653,12 +7823,49 @@
of the construction of the values.
2. Unescaped characters are copied as is.
+ @pre `m_cursor - m_start >= 2`, meaning the length of the last token
+ is at least 2 bytes which is trivially true for any string (which
+ consists of at least two quotes).
+
+ " c1 c2 c3 ... "
+ ^ ^
+ m_start m_cursor
+
+ @complexity Linear in the length of the string.\n
+
+ Lemma: The loop body will always terminate.\n
+
+ Proof (by contradiction): Assume the loop body does not terminate. As
+ the loop body does not contain another loop, one of the called
+ functions must never return. The called functions are `std::strtoul`
+ and to_unicode. Neither function can loop forever, so the loop body
+ will never loop forever which contradicts the assumption that the loop
+ body does not terminate, q.e.d.\n
+
+ Lemma: The loop condition for the for loop is eventually false.\n
+
+ Proof (by contradiction): Assume the loop does not terminate. Due to
+ the above lemma, this can only be due to a tautological loop
+ condition; that is, the loop condition i < m_cursor - 1 must always be
+ true. Let x be the change of i for any loop iteration. Then
+ m_start + 1 + x < m_cursor - 1 must hold to loop indefinitely. This
+ can be rephrased to m_cursor - m_start - 2 > x. With the
+ precondition, we x <= 0, meaning that the loop condition holds
+ indefinitly if i is always decreased. However, observe that the value
+ of i is strictly increasing with each iteration, as it is incremented
+ by 1 in the iteration expression and never decremented inside the loop
+ body. Hence, the loop condition will eventually be false which
+ contradicts the assumption that the loop condition is a tautology,
+ q.e.d.
+
@return string value of current token without opening and closing
quotes
@throw std::out_of_range if to_unicode fails
*/
string_t get_string() const
{
+ assert(m_cursor - m_start >= 2);
+
string_t result;
result.reserve(static_cast<size_t>(m_cursor - m_start - 2));
@@ -7774,11 +7981,6 @@
the number
@return the floating point number
-
- @bug This function uses `std::strtof`, `std::strtod`, or `std::strtold`
- which use the current C locale to determine which character is used as
- decimal point character. This may yield to parse errors if the locale
- does not used `.`.
*/
long double str_to_float_t(long double* /* type */, char** endptr) const
{
@@ -7959,7 +8161,7 @@
{
public:
/// constructor for strings
- parser(const string_t& s, parser_callback_t cb = nullptr,
+ parser(const string_t& s, const parser_callback_t cb = nullptr,
const allocator_type& allocator = {}) noexcept
: callback(cb), m_lexer(s), m_allocator(allocator)
{
@@ -7968,7 +8170,7 @@
}
/// a parser reading from an input stream
- parser(std::istream& _is, parser_callback_t cb = nullptr,
+ parser(std::istream& _is, const parser_callback_t cb = nullptr,
const allocator_type& allocator = {}) noexcept
: callback(cb), m_lexer(&_is), m_allocator(allocator)
{
@@ -8217,7 +8419,7 @@
/// current level of recursion
int depth = 0;
/// callback function
- parser_callback_t callback;
+ const parser_callback_t callback = nullptr;
/// the type of the last read token
typename lexer::token_type last_token = lexer::token_type::uninitialized;
/// the lexer
@@ -8335,6 +8537,8 @@
/*!
@brief create and return a reference to the pointed to value
+
+ @complexity Linear in the number of reference tokens.
*/
reference get_and_create(reference j) const
{
@@ -8656,7 +8860,7 @@
@param[in,out] s the string to manipulate
@param[in] f the substring to replace with @a t
- @param[out] t the string to replace @a f
+ @param[in] t the string to replace @a f
@return The string @a s where all occurrences of @a f are replaced
with @a t.
@@ -8772,6 +8976,7 @@
basic_json result;
// iterate the JSON object values
+ assert(value.m_value.object != nullptr);
for (const auto& element : *value.m_value.object)
{
if (not element.second.is_primitive())
@@ -9512,7 +9717,7 @@
@brief user-defined string literal for JSON values
This operator implements a user-defined string literal for JSON objects. It
-can be used by adding \p "_json" to a string literal and returns a JSON object
+can be used by adding `"_json"` to a string literal and returns a JSON object
if no parse error occurred.
@param[in] s a string representation of a JSON object
@@ -9528,6 +9733,13 @@
/*!
@brief user-defined string literal for JSON pointer
+This operator implements a user-defined string literal for JSON Pointers. It
+can be used by adding `"_json"` to a string literal and returns a JSON pointer
+object if no parse error occurred.
+
+@param[in] s a string representation of a JSON Pointer
+@return a JSON pointer object
+
@since version 2.0.0
*/
inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t)
diff --git a/test/src/unit.cpp b/test/src/unit.cpp
index f89e066..ff82ebd 100644
--- a/test/src/unit.cpp
+++ b/test/src/unit.cpp
@@ -1755,6 +1755,28 @@
ja.dump();
}
*/
+
+ SECTION("check that precision is reset after serialization")
+ {
+ // create stringstream and set precision
+ std::stringstream ss;
+ ss.precision(3);
+ ss << 3.141592653589793 << std::fixed;
+ CHECK(ss.str() == "3.14");
+
+ // reset stringstream
+ ss.str(std::string());
+
+ // use stringstream for JSON serialization
+ json j_number = 3.141592653589793;
+ ss << j_number;
+
+ // check that precision has been overridden during serialization
+ CHECK(ss.str() == "3.141592653589793");
+
+ // check that precision has been restored
+ CHECK(ss.precision() == 3);
+ }
}
SECTION("return the type of the object (explicit)")
@@ -3816,123 +3838,254 @@
SECTION("access specified element with default value")
{
- SECTION("access existing value")
+ SECTION("given a key")
{
- 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");
- CHECK(j.value("string", std::string("bar")) == "hello world");
- CHECK(j.value("floating", 12.34) == Approx(42.23));
- CHECK(j.value("floating", 12) == 42);
- CHECK(j.value("object", json({{"foo", "bar"}})) == json(json::object()));
- CHECK(j.value("array", json({10, 100})) == json({1, 2, 3}));
+ SECTION("access existing value")
+ {
+ 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");
+ CHECK(j.value("string", std::string("bar")) == "hello world");
+ CHECK(j.value("floating", 12.34) == Approx(42.23));
+ CHECK(j.value("floating", 12) == 42);
+ CHECK(j.value("object", json({{"foo", "bar"}})) == json(json::object()));
+ CHECK(j.value("array", json({10, 100})) == json({1, 2, 3}));
- 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");
- CHECK(j_const.value("floating", 12.34) == Approx(42.23));
- CHECK(j_const.value("floating", 12) == 42);
- CHECK(j_const.value("object", json({{"foo", "bar"}})) == json(json::object()));
- CHECK(j_const.value("array", json({10, 100})) == json({1, 2, 3}));
+ 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");
+ CHECK(j_const.value("floating", 12.34) == Approx(42.23));
+ CHECK(j_const.value("floating", 12) == 42);
+ CHECK(j_const.value("object", json({{"foo", "bar"}})) == json(json::object()));
+ CHECK(j_const.value("array", json({10, 100})) == json({1, 2, 3}));
+ }
+
+ 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));
+ CHECK(j.value("_", json({{"foo", "bar"}})) == json({{"foo", "bar"}}));
+ 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));
+ CHECK(j_const.value("_", json({{"foo", "bar"}})) == json({{"foo", "bar"}}));
+ CHECK(j_const.value("_", json({10, 100})) == json({10, 100}));
+ }
+
+ SECTION("access on non-object type")
+ {
+ SECTION("null")
+ {
+ json j_nonobject(json::value_t::null);
+ 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 null");
+ CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with null");
+ }
+
+ SECTION("boolean")
+ {
+ json j_nonobject(json::value_t::boolean);
+ 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 boolean");
+ CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with boolean");
+ }
+
+ SECTION("string")
+ {
+ json j_nonobject(json::value_t::string);
+ 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 string");
+ CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with string");
+ }
+
+ SECTION("array")
+ {
+ json j_nonobject(json::value_t::array);
+ 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 array");
+ CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with array");
+ }
+
+ SECTION("number (integer)")
+ {
+ json j_nonobject(json::value_t::number_integer);
+ 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 (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);
+ 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("access non-existing value")
+ SECTION("given a JSON pointer")
{
- 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));
- CHECK(j.value("_", json({{"foo", "bar"}})) == json({{"foo", "bar"}}));
- 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));
- CHECK(j_const.value("_", json({{"foo", "bar"}})) == json({{"foo", "bar"}}));
- CHECK(j_const.value("_", json({10, 100})) == json({10, 100}));
- }
-
- SECTION("access on non-object type")
- {
- SECTION("null")
+ SECTION("access existing value")
{
- json j_nonobject(json::value_t::null);
- 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 null");
- CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with null");
+ CHECK(j.value("/integer"_json_pointer, 2) == 1);
+ CHECK(j.value("/integer"_json_pointer, 1.0) == Approx(1));
+ CHECK(j.value("/unsigned"_json_pointer, 2) == 1u);
+ CHECK(j.value("/unsigned"_json_pointer, 1.0) == Approx(1u));
+ CHECK(j.value("/null"_json_pointer, json(1)) == json());
+ CHECK(j.value("/boolean"_json_pointer, false) == true);
+ CHECK(j.value("/string"_json_pointer, "bar") == "hello world");
+ CHECK(j.value("/string"_json_pointer, std::string("bar")) == "hello world");
+ CHECK(j.value("/floating"_json_pointer, 12.34) == Approx(42.23));
+ CHECK(j.value("/floating"_json_pointer, 12) == 42);
+ CHECK(j.value("/object"_json_pointer, json({{"foo", "bar"}})) == json(json::object()));
+ CHECK(j.value("/array"_json_pointer, json({10, 100})) == json({1, 2, 3}));
+
+ CHECK(j_const.value("/integer"_json_pointer, 2) == 1);
+ CHECK(j_const.value("/integer"_json_pointer, 1.0) == Approx(1));
+ CHECK(j_const.value("/unsigned"_json_pointer, 2) == 1u);
+ CHECK(j_const.value("/unsigned"_json_pointer, 1.0) == Approx(1u));
+ CHECK(j_const.value("/boolean"_json_pointer, false) == true);
+ CHECK(j_const.value("/string"_json_pointer, "bar") == "hello world");
+ CHECK(j_const.value("/string"_json_pointer, std::string("bar")) == "hello world");
+ CHECK(j_const.value("/floating"_json_pointer, 12.34) == Approx(42.23));
+ CHECK(j_const.value("/floating"_json_pointer, 12) == 42);
+ CHECK(j_const.value("/object"_json_pointer, json({{"foo", "bar"}})) == json(json::object()));
+ CHECK(j_const.value("/array"_json_pointer, json({10, 100})) == json({1, 2, 3}));
}
- SECTION("boolean")
+ SECTION("access non-existing value")
{
- json j_nonobject(json::value_t::boolean);
- 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 boolean");
- CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with boolean");
+ CHECK(j.value("/not/existing"_json_pointer, 2) == 2);
+ CHECK(j.value("/not/existing"_json_pointer, 2u) == 2u);
+ CHECK(j.value("/not/existing"_json_pointer, false) == false);
+ CHECK(j.value("/not/existing"_json_pointer, "bar") == "bar");
+ CHECK(j.value("/not/existing"_json_pointer, 12.34) == Approx(12.34));
+ CHECK(j.value("/not/existing"_json_pointer, json({{"foo", "bar"}})) == json({{"foo", "bar"}}));
+ CHECK(j.value("/not/existing"_json_pointer, json({10, 100})) == json({10, 100}));
+
+ CHECK(j_const.value("/not/existing"_json_pointer, 2) == 2);
+ CHECK(j_const.value("/not/existing"_json_pointer, 2u) == 2u);
+ CHECK(j_const.value("/not/existing"_json_pointer, false) == false);
+ CHECK(j_const.value("/not/existing"_json_pointer, "bar") == "bar");
+ CHECK(j_const.value("/not/existing"_json_pointer, 12.34) == Approx(12.34));
+ CHECK(j_const.value("/not/existing"_json_pointer, json({{"foo", "bar"}})) == json({{"foo", "bar"}}));
+ CHECK(j_const.value("/not/existing"_json_pointer, json({10, 100})) == json({10, 100}));
}
- SECTION("string")
+ SECTION("access on non-object type")
{
- json j_nonobject(json::value_t::string);
- 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 string");
- CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with string");
- }
+ SECTION("null")
+ {
+ json j_nonobject(json::value_t::null);
+ const json j_nonobject_const(j_nonobject);
+ CHECK_THROWS_AS(j_nonobject.value("/foo"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_AS(j_nonobject_const.value("/foo"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_WITH(j_nonobject.value("/foo"_json_pointer, 1), "cannot use value() with null");
+ CHECK_THROWS_WITH(j_nonobject_const.value("/foo"_json_pointer, 1), "cannot use value() with null");
+ }
- SECTION("array")
- {
- json j_nonobject(json::value_t::array);
- 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 array");
- CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with array");
- }
+ SECTION("boolean")
+ {
+ json j_nonobject(json::value_t::boolean);
+ const json j_nonobject_const(j_nonobject);
+ CHECK_THROWS_AS(j_nonobject.value("/foo"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_AS(j_nonobject_const.value("/foo"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_WITH(j_nonobject.value("/foo"_json_pointer, 1), "cannot use value() with boolean");
+ CHECK_THROWS_WITH(j_nonobject_const.value("/foo"_json_pointer, 1),
+ "cannot use value() with boolean");
+ }
- SECTION("number (integer)")
- {
- json j_nonobject(json::value_t::number_integer);
- 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("string")
+ {
+ json j_nonobject(json::value_t::string);
+ const json j_nonobject_const(j_nonobject);
+ CHECK_THROWS_AS(j_nonobject.value("/foo"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_AS(j_nonobject_const.value("/foo"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_WITH(j_nonobject.value("/foo"_json_pointer, 1), "cannot use value() with string");
+ CHECK_THROWS_WITH(j_nonobject_const.value("/foo"_json_pointer, 1),
+ "cannot use value() with string");
+ }
- 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("array")
+ {
+ json j_nonobject(json::value_t::array);
+ const json j_nonobject_const(j_nonobject);
+ CHECK_THROWS_AS(j_nonobject.value("/foo"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_AS(j_nonobject_const.value("/foo"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_WITH(j_nonobject.value("/foo"_json_pointer, 1), "cannot use value() with array");
+ CHECK_THROWS_WITH(j_nonobject_const.value("/foo"_json_pointer, 1), "cannot use value() with array");
+ }
- SECTION("number (floating-point)")
- {
- json j_nonobject(json::value_t::number_float);
- 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 (integer)")
+ {
+ json j_nonobject(json::value_t::number_integer);
+ const json j_nonobject_const(j_nonobject);
+ CHECK_THROWS_AS(j_nonobject.value("/foo"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_AS(j_nonobject_const.value("/foo"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_WITH(j_nonobject.value("/foo"_json_pointer, 1), "cannot use value() with number");
+ CHECK_THROWS_WITH(j_nonobject_const.value("/foo"_json_pointer, 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"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_AS(j_nonobject_const.value("/foo"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_WITH(j_nonobject.value("/foo"_json_pointer, 1), "cannot use value() with number");
+ CHECK_THROWS_WITH(j_nonobject_const.value("/foo"_json_pointer, 1),
+ "cannot use value() with number");
+ }
+
+ SECTION("number (floating-point)")
+ {
+ json j_nonobject(json::value_t::number_float);
+ const json j_nonobject_const(j_nonobject);
+ CHECK_THROWS_AS(j_nonobject.value("/foo"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_AS(j_nonobject_const.value("/foo"_json_pointer, 1), std::domain_error);
+ CHECK_THROWS_WITH(j_nonobject.value("/foo"_json_pointer, 1), "cannot use value() with number");
+ CHECK_THROWS_WITH(j_nonobject_const.value("/foo"_json_pointer, 1),
+ "cannot use value() with number");
+ }
}
}
}
@@ -9774,6 +9927,39 @@
CHECK_THROWS_WITH(json::parser("\"\b\"").parse(), "parse error - unexpected '\"'");
// improve code coverage
CHECK_THROWS_AS(json::parser("\uFF01").parse(), std::invalid_argument);
+ // unescaped control characters
+ CHECK_THROWS_AS(json::parser("\"\x00\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x01\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x02\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x03\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x04\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x05\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x06\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x07\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x08\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x09\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x0a\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x0b\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x0c\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x0d\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x0e\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x0f\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x10\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x11\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x12\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x13\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x14\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x15\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x16\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x17\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x18\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x19\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x1a\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x1b\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x1c\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x1d\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x1e\"").parse(), std::invalid_argument);
+ CHECK_THROWS_AS(json::parser("\"\x1f\"").parse(), std::invalid_argument);
}
SECTION("escaped")
@@ -10384,6 +10570,14 @@
CHECK(j_empty_array == json());
}
}
+
+ SECTION("copy constructor")
+ {
+ json::string_t* s = new json::string_t("[1,2,3,4]");
+ json::parser p(*s);
+ delete s;
+ CHECK(p.parse() == json({1, 2, 3, 4}));
+ }
}
TEST_CASE("README", "[hide]")
@@ -12171,19 +12365,23 @@
TEST_CASE("Unicode", "[hide]")
{
- SECTION("full enumeration of Unicode codepoints")
+ SECTION("full enumeration of Unicode code points")
{
- // create a string from a codepoint
- auto codepoint_to_unicode = [](std::size_t cp)
+ // create an escaped string from a code point
+ const auto codepoint_to_unicode = [](std::size_t cp)
{
- char* buffer = new char[10];
- sprintf(buffer, "\\u%04lx", cp);
- std::string result(buffer);
- delete[] buffer;
- return result;
+ // copd points are represented as a six-character sequence: a
+ // reverse solidus, followed by the lowercase letter u, followed
+ // by four hexadecimal digits that encode the character's code
+ // point
+ std::stringstream ss;
+ ss << "\\u" << std::setw(4) << std::setfill('0') << std::hex << cp;
+ return ss.str();
};
- // generate all codepoints
+ // generate all UTF-8 code points; in total, 1112064 code points are
+ // generated: 0x1FFFFF code points - 2048 invalid values between
+ // 0xD800 and 0xDFFF.
for (std::size_t cp = 0; cp <= 0x10FFFFu; ++cp)
{
// The Unicode standard permanently reserves these code point
@@ -12193,34 +12391,57 @@
// no UTF forms, including UTF-16, can encode these code points.
if (cp >= 0xD800u and cp <= 0xDFFFu)
{
+ // if we would not skip these code points, we would get a
+ // "missing low surrogate" exception
continue;
}
- std::string res;
+ // string to store the code point as in \uxxxx format
+ std::string escaped_string;
+ // string to store the code point as unescaped character sequence
+ std::string unescaped_string;
if (cp < 0x10000u)
{
- // codepoint can be represented with 16 bit
- res += codepoint_to_unicode(cp);
+ // code points in the Basic Multilingual Plane can be
+ // represented with one \\uxxxx sequence
+ escaped_string = codepoint_to_unicode(cp);
+
+ // All Unicode characters may be placed within the quotation
+ // marks, except for the characters that must be escaped:
+ // quotation mark, reverse solidus, and the control characters
+ // (U+0000 through U+001F); we ignore these code points as
+ // they are checked with codepoint_to_unicode.
+ if (cp > 0x1f and cp != 0x22 and cp != 0x5c)
+ {
+ unescaped_string = json::lexer::to_unicode(cp);
+ }
}
else
{
- // codepoint can be represented with a pair
- res += codepoint_to_unicode(0xd800u + (((cp - 0x10000u) >> 10) & 0x3ffu));
- res += codepoint_to_unicode(0xdc00u + ((cp - 0x10000u) & 0x3ffu));
+ // To escape an extended character that is not in the Basic
+ // Multilingual Plane, the character is represented as a
+ // 12-character sequence, encoding the UTF-16 surrogate pair
+ const auto codepoint1 = 0xd800u + (((cp - 0x10000u) >> 10) & 0x3ffu);
+ const auto codepoint2 = 0xdc00u + ((cp - 0x10000u) & 0x3ffu);
+ escaped_string = codepoint_to_unicode(codepoint1);
+ escaped_string += codepoint_to_unicode(codepoint2);
+ unescaped_string += json::lexer::to_unicode(codepoint1, codepoint2);
}
- try
- {
- json j1, j2;
- CHECK_NOTHROW(j1 = json::parse("\"" + res + "\""));
- CHECK_NOTHROW(j2 = json::parse(j1.dump()));
- CHECK(j1 == j2);
- }
- catch (std::invalid_argument)
- {
- // we ignore parsing errors
- }
+ // all other code points are valid and must not yield parse errors
+ CAPTURE(cp);
+ CAPTURE(escaped_string);
+ CAPTURE(unescaped_string);
+
+ json j1, j2, j3, j4;
+ CHECK_NOTHROW(j1 = json::parse("\"" + escaped_string + "\""));
+ CHECK_NOTHROW(j2 = json::parse(j1.dump()));
+ CHECK(j1 == j2);
+
+ CHECK_NOTHROW(j3 = json::parse("\"" + unescaped_string + "\""));
+ CHECK_NOTHROW(j4 = json::parse(j3.dump()));
+ CHECK(j3 == j4);
}
}
@@ -12233,6 +12454,8 @@
CHECK_NOTHROW(j << f);
// the array has 1112064 + 1 elemnts (a terminating "null" value)
+ // Note: 1112064 = 0x1FFFFF code points - 2048 invalid values between
+ // 0xD800 and 0xDFFF.
CHECK(j.size() == 1112065);
SECTION("check JSON Pointers")
@@ -14217,6 +14440,19 @@
// check roundtrip
CHECK(doc.patch(json::diff(doc, expected)) == expected);
}
+
+ SECTION("issue #283 - value() does not work with _json_pointer types")
+ {
+ json j =
+ {
+ {"object", {{"key1", 1}, {"key2", 2}}},
+ };
+
+ int at_integer = j.at("/object/key2"_json_pointer);
+ int val_integer = j.value("/object/key2"_json_pointer, 0);
+
+ CHECK(at_integer == val_integer);
+ }
}
TEST_CASE("custom Allocators")