diff --git a/README.md b/README.md index 69676370ad..b64c141711 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ - [Conversion from STL containers](#conversion-from-stl-containers) - [JSON Pointer and JSON Patch](#json-pointer-and-json-patch) - [Implicit conversions](#implicit-conversions) + - [Conversions to arbitrary types](#arbitrary-types-conversions) - [Binary formats (CBOR and MessagePack)](#binary-formats-cbor-and-messagepack) - [Supported compilers](#supported-compilers) - [License](#license) @@ -441,6 +442,218 @@ int vi = jn.get(); // etc. ``` +### Arbitrary types conversions + +Every type can be serialized in JSON, not just STL-containers and scalar types. +Usually, you would do something along those lines: + +```cpp +namespace ns { +struct person { std::string name; std::string address; int age; }; +} +// convert to JSON +json j; +ns::person p = createSomeone(); +j["name"] = p.name; +j["address"] = p.address; +j["age"] = p.age; + +// ... + +// convert from JSON +ns::person p {j["name"].get(), j["address"].get(), j["age"].get()}; +``` + +It works, but that's quite a lot of boilerplate.. Hopefully, there's a better way: + +```cpp +ns::person p = createPerson(); +json j = p; + +auto p2 = j.get(); +assert(p == p2); +``` + +#### Basic usage + +To make this work with one of your types, you only need to provide two methods: + +```cpp +using nlohmann::json; + +namespace ns { +void to_json(json& j, person const& p) +{ + j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}}; +} + +void from_json(json const& j, person& p) +{ + p.name = j["name"].get(); + p.address = j["address"].get(); + p.age = j["age"].get(); +} +} // namespace ns +``` + +That's all. When calling the json constructor with your type, your custom `to_json` method will be automatically called. +Likewise, when calling `get()`, the `from_json` method will be called. + +Some important things: + +* Those methods **MUST** be in your type's namespace, or the library will not be able to locate them (in this example, they are in namespace `ns`, where `person` is defined). +* When using `get()`, `your_type` **MUST** be DefaultConstructible and CopyConstructible (There is a way to bypass those requirements described later) + +#### How do I convert third-party types? + +This requires a bit more advanced technique. +But first, let's see how this conversion mechanism works: + +The library uses **JSON Serializers** to convert types to json. +The default serializer for `nlohmann::json` is `nlohmann::adl_serializer` (ADL means [Argument-Dependent Lookup](http://en.cppreference.com/w/cpp/language/adl)) + +It is implemented like this (simplified): + +```cpp +template +struct adl_serializer +{ + static void to_json(json& j, const T& value) + { + // calls the "to_json" method in T's namespace + } + + static void from_json(const json& j, T& value) + { + // same thing, but with the "from_json" method + } +}; +``` + +This serializer works fine when you have control over the type's namespace. +However, what about `boost::optional`, or `std::filesystem::path` (C++17)? + +Hijacking the `boost` namespace is pretty bad, and it's illegal to add something other than template specializations to `std`... + +To solve this, you need to add a specialization of `adl_serializer` to the `nlohmann` namespace, here's an example: + +```cpp +// partial specialization (full specialization works too) +namespace nlohmann { +template +struct adl_serializer> +{ + static void to_json(json& j, const boost::optional& opt) + { + if (opt == boost::none) + j = nullptr; + else + j = *opt; // this will call adl_serializer::to_json, which will find the free function to_json in T's namespace! + } + + static void from_json(const json& j, boost::optional& opt) + { + if (!j.is_null()) + opt = j.get(); // same as above, but with adl_serializer::from_json + } +}; +} +``` + +#### How can I use `get()` for non-default constructible/non-copyable types? + +There is a way, if your type is **MoveConstructible**. +You will need to specialize the `adl_serializer` as well, but with a special `from_json` overload: + +```cpp +struct move_only_type { + move_only_type() = delete; + move_only_type(int ii): i(ii) {} + move_only_type(const move_only_type&) = delete; + move_only_type(move_only_type&&) = default; + : + int i; +}; + +namespace nlohmann { +template <> +struct adl_serializer +{ + // note: the return type is no longer 'void', and the method only takes one argument + static move_only_type from_json(const json& j) + { + return {j.get()}; + } + + // Here's the catch! You must provide a to_json method! + // Otherwise you will not be able to convert move_only_type to json, + // since you fully specialized adl_serializer on that type + static void to_json(json& j, move_only_type t) + { + j = t.i; + } +}; +} +``` + +#### Can I write my own serializer? (Advanced use) + +Yes. You might want to take a look at `unit-udt.cpp` in the test suite, to see a few examples. + +If you write your own serializer, you'll need to do a few things: + +* use a different `basic_json` alias than nlohmann::json (the last template parameter of basic_json is the JSONSerializer) +* use your `basic_json` alias (or a template parameter) in all your `to_json`/`from_json` methods +* use `nlohmann::to_json` and `nlohmann::from_json` when you need ADL + +Here is an example, without simplifications, that only accepts types with a size <= 32, and uses ADL. + +```cpp +// You should use void as a second template argument if you don't need compile-time checks on T +template ::type> +struct less_than_32_serializer // if someone tries to use a type bigger than 32, the compiler will complain +{ + template + static void to_json(Json& j, T value) + { + // we want to use ADL, and call the correct to_json overload + using nlohmann::to_json; // this method is called by adl_serializer, this is where the magic happens + to_json(j, value); + } + + template + static void from_json(const Json& j, T& value) + { + // same thing here + using nlohmann::from_json; + from_json(j, value); + } +}; +``` + +Be **very** careful when reimplementing your serializer, you can stack overflow if you don't pay attention: + +```cpp +template +struct bad_serializer +{ + template + static void to_json(Json& j, const T& value) + { + // this calls Json::json_serializer::to_json(j, value); + // if Json::json_serializer == bad_serializer ... oops! + j = value; + } + + template + static void to_json(const Json& j, T& value) + { + // this calls Json::json_serializer::from_json(j, value); + // if Json::json_serializer == bad_serializer ... oops! + value = j.template get(); // oops! + } +}; +``` ### Binary formats (CBOR and MessagePack) diff --git a/doc/Doxyfile b/doc/Doxyfile index 5064a0a0c5..4d511af319 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -109,7 +109,7 @@ RECURSIVE = NO EXCLUDE = EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = -EXCLUDE_SYMBOLS = nlohmann::anonymous_namespace +EXCLUDE_SYMBOLS = nlohmann::detail EXAMPLE_PATH = examples EXAMPLE_PATTERNS = EXAMPLE_RECURSIVE = NO diff --git a/doc/examples/type_name.cpp b/doc/examples/type_name.cpp new file mode 100644 index 0000000000..815e92d7e0 --- /dev/null +++ b/doc/examples/type_name.cpp @@ -0,0 +1,24 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create JSON values + json j_null; + json j_boolean = true; + json j_number_integer = 17; + json j_number_float = 23.42; + json j_object = {{"one", 1}, {"two", 2}}; + json j_array = {1, 2, 4, 8, 16}; + json j_string = "Hello, world"; + + // call type_name() + std::cout << j_null.type_name() << '\n'; + std::cout << j_boolean.type_name() << '\n'; + std::cout << j_number_integer.type_name() << '\n'; + std::cout << j_number_float.type_name() << '\n'; + std::cout << j_object.type_name() << '\n'; + std::cout << j_array.type_name() << '\n'; + std::cout << j_string.type_name() << '\n'; +} diff --git a/doc/examples/type_name.link b/doc/examples/type_name.link new file mode 100644 index 0000000000..39d1f973ee --- /dev/null +++ b/doc/examples/type_name.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/type_name.output b/doc/examples/type_name.output new file mode 100644 index 0000000000..ad906a4977 --- /dev/null +++ b/doc/examples/type_name.output @@ -0,0 +1,7 @@ +null +boolean +number +number +object +array +string diff --git a/src/json.hpp b/src/json.hpp index fa9b85f263..7f17a55540 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -39,6 +39,7 @@ SOFTWARE. #include // int64_t, uint64_t #include // strtod, strtof, strtold, strtoul #include // strlen +#include // forward_list #include // function, hash, less #include // initializer_list #include // setw @@ -106,39 +107,811 @@ SOFTWARE. */ namespace nlohmann { +// TODO update this doc +/*! +@brief unnamed namespace with internal helper functions +@since version 1.0.0 +*/ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// /*! -@brief unnamed namespace with internal helper functions +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used +to distinguish the stored values, and the functions @ref basic_json::is_null(), @ref +basic_json::is_object(), @ref basic_json::is_array(), @ref basic_json::is_string(), @ref basic_json::is_boolean(), @ref +basic_json::is_number() (with @ref basic_json::is_number_integer(), @ref basic_json::is_number_unsigned(), and +@ref basic_json::is_number_float()), @ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::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 basic_json::number_unsigned_t is used for unsigned +integers, @ref basic_json::number_integer_t is used for signed integers, and @ref +basic_json::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::basic_json(const value_t value_type) -- create a JSON value with +the default value for a given type + @since version 1.0.0 */ -namespace +enum class value_t : uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + 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 +}; + +////////////////////////////////////////// +// lexicographical comparison operators // +////////////////////////////////////////// + +/// @name lexicographical comparison operators +/// @{ +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string +- furthermore, each type is not smaller than itself + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array order = {{ + 0, // null + 3, // object + 4, // array + 5, // string + 1, // boolean + 2, // integer + 2, // unsigned + 2, // float + } + }; + + // discarded values are not comparable + if (lhs == value_t::discarded or rhs == value_t::discarded) + { + return false; + } + + return order[static_cast(lhs)] < + order[static_cast(rhs)]; +} + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// Taken from http://stackoverflow.com/questions/26936640/how-to-implement-is-enum-class-type-trait +template +using is_unscoped_enum = + std::integral_constant::value and + std::is_enum::value>; + +// Implementation of 2 C++17 constructs: conjunction, negation. +// This is needed to avoid evaluating all the traits in a condition +// +// For example: not std::is_same::value and has_value_type::value +// will not compile when T = void (on MSVC at least) +// Whereas conjunction>, has_value_type>::value +// will stop evaluating if negation<...>::value == false +// +// Please note that those constructs must be used with caution, since symbols can +// become very long quickly (which can slow down compilation and cause MSVC internal compiler errors) +// Only use it when you have too (see example ahead) +template struct conjunction : std::true_type {}; +template struct conjunction : B1 {}; +template +struct conjunction +: std::conditional, B1>::type {}; + +template struct negation : std::integral_constant < bool, !B::value > {}; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; + +template <> struct priority_tag<0> {}; + +// This is an experiment. I need this to move constructors out of basic_json. +// I'm sure there is a better way, but this might need a big basic_json refactoring +template struct external_constructor; + +template <> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept + { + j.m_type = value_t::boolean; + j.m_value = b; + j.assert_invariant(); + } +}; + +template <> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) + { + j.m_type = value_t::string; + j.m_value = s; + j.assert_invariant(); + } +}; + +template <> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept + { + // replace infinity and NAN by null + if (not std::isfinite(val)) + j = BasicJsonType{}; + else + { + j.m_type = value_t::number_float; + j.m_value = val; + } + j.assert_invariant(); + } +}; + +template <> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept + { + j.m_type = value_t::number_unsigned; + j.m_value = val; + j.assert_invariant(); + } +}; + +template <> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept + { + j.m_type = value_t::number_integer; + j.m_value = val; + j.assert_invariant(); + } +}; + +template <> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template ::value, + int> = 0> + static void construct(BasicJsonType& j, const CompatibleArrayType& arr) + { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = + j.template create(begin(arr), end(arr)); + j.assert_invariant(); + } +}; + +template <> +struct external_constructor { + template + static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) + { + j.m_type = value_t::object; + j.m_value = obj; + j.assert_invariant(); + } + + template ::value, + int> = 0> + static void construct(BasicJsonType& j, const CompatibleObjectType& obj) + { + using std::begin; + using std::end; + + j.m_type = value_t::object; + j.m_value.object = + j.template create(begin(obj), end(obj)); + j.assert_invariant(); + } +}; + /*! @brief Helper to determine whether there's a key_type for T. -Thus helper is used to tell associative containers apart from other containers +This helper is used to tell associative containers apart from other containers such as sequence containers. For instance, `std::map` passes the test as it contains a `mapped_type`, whereas `std::vector` fails the test. @sa http://stackoverflow.com/a/7728728/266378 @since version 1.0.0, overworked in version 2.0.6 */ -template -struct has_mapped_type +#define NLOHMANN_JSON_HAS_HELPER(type) \ + template struct has_##type { \ + private: \ + template \ + static int detect(U &&); \ + static void detect(...); \ + public: \ + static constexpr bool value = \ + std::is_integral()))>::value; \ + } + +NLOHMANN_JSON_HAS_HELPER(mapped_type); +NLOHMANN_JSON_HAS_HELPER(key_type); +NLOHMANN_JSON_HAS_HELPER(value_type); +NLOHMANN_JSON_HAS_HELPER(iterator); + +#undef NLOHMANN_JSON_HAS_HELPER + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl +{ + static constexpr auto value = + std::is_constructible::value and + std::is_constructible::value; +}; + +template +struct is_compatible_object_type +{ + static auto constexpr value = is_compatible_object_type_impl < + conjunction>, + has_mapped_type, + has_key_type>::value, + typename BasicJsonType::object_t, CompatibleObjectType >::value; +}; + +template +struct is_basic_json_nested_type +{ + static auto constexpr value = std::is_same::value or + std::is_same::value or + std::is_same::value or + std::is_same::value or + std::is_same::value; +}; + +template +struct is_compatible_array_type +{ + // TODO concept Container? + // this might not make VS happy + static auto constexpr value = + conjunction>, + negation>, + negation>, + negation>, + has_value_type, + has_iterator>::value; +}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + std::is_constructible::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type +{ + static constexpr auto + value = is_compatible_integer_type_impl < + std::is_integral::value and + not std::is_same::value, + RealIntegerType, CompatibleNumberIntegerType > ::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json { private: - template + // also check the return type of from_json + template ::from_json( + std::declval(), std::declval()))>::value>> static int detect(U&&); + static void detect(...); + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json +{ + private: + template < + typename U, + typename = enable_if_t::from_json(std::declval()))>::value >> + static int detect(U&&); static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +template +struct has_to_json +{ + private: + template ::to_json( + std::declval(), std::declval()))> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// overloads for basic_json template parameters + +template ::value and + not std::is_same::value, + int> = 0> +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + val = static_cast( + *j.template get_ptr()); + break; + case value_t::number_integer: + val = static_cast( + *j.template get_ptr()); + break; + case value_t::number_float: + val = static_cast( + *j.template get_ptr()); + break; + default: + JSON_THROW( + std::domain_error("type must be number, but is " + j.type_name())); + } +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept +{ + external_constructor::construct(j, b); +} + +template ::value, + int> = 0> +void to_json(BasicJsonType& j, const CompatibleString& s) +{ + external_constructor::construct(j, s); +} + +template ::value, int> = 0> +void to_json(BasicJsonType& j, FloatType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + + +template < + typename BasicJsonType, typename CompatibleNumberUnsignedType, + enable_if_t::value, + int> = 0 > +void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template < + typename BasicJsonType, typename CompatibleNumberIntegerType, + enable_if_t::value, + int> = 0 > +void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template ::value, int> = 0> +void to_json(BasicJsonType& j, UnscopedEnumType e) noexcept +{ + external_constructor::construct(j, e); +} + +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < + is_compatible_array_type::value or + std::is_same::value, + int > = 0 > +void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +{ + external_constructor::construct(j, arr); +} + +template < + typename BasicJsonType, typename CompatibleObjectType, + enable_if_t::value, + int> = 0 > +void to_json(BasicJsonType& j, const CompatibleObjectType& arr) +{ + external_constructor::construct(j, arr); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (!j.is_boolean()) + { + JSON_THROW(std::domain_error("type must be boolean, but is " + j.type_name())); + } + b = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (!j.is_string()) + { + JSON_THROW(std::domain_error("type must be string, but is " + j.type_name())); + } + s = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +template ::value, int> = 0> +void from_json(const BasicJsonType& j, UnscopedEnumType& e) +{ + typename std::underlying_type::type val = e; + get_arithmetic_value(j, val); + e = static_cast(val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) +{ + if (!j.is_array()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + arr = *j.template get_ptr(); +} + +// forward_list doesn't have an insert method, TODO find a way to avoid including forward_list +template +void from_json(const BasicJsonType& j, std::forward_list& l) +{ + // do not perform the check when user wants to retrieve jsons + // (except when it's null.. ?) + if (j.is_null()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + if (not std::is_same::value) + { + if (!j.is_array()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + } + for (auto it = j.rbegin(), end = j.rend(); it != end; ++it) + { + l.push_front(it->template get()); + } +} + +template +void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0>) +{ + using std::begin; + using std::end; + + std::transform( + j.begin(), j.end(), std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json method when + // value_type is BasicJsonType + return i.template get(); + }); +} + +template +auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1>) +-> decltype( + arr.reserve(std::declval()), + void()) +{ + using std::begin; + using std::end; + + arr.reserve(j.size()); + std::transform( + j.begin(), j.end(), std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json method when + // value_type is BasicJsonType + return i.template get(); + }); +} + +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t::value and + not std::is_same::value, + int> = 0 > +void from_json(const BasicJsonType& j, CompatibleArrayType& arr) +{ + if (j.is_null()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + // when T == BasicJsonType, do not check if value_t is correct + if (not std::is_same::value) + { + if (!j.is_array()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + } + from_json_array_impl(j, arr, priority_tag<1> {}); +} + + +template < + typename BasicJsonType, typename CompatibleObjectType, + enable_if_t::value, + int> = 0 > +void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +{ + if (!j.is_object()) + { + JSON_THROW(std::domain_error("type must be object, but is " + j.type_name())); + } + + auto inner_object = j.template get_ptr(); + using std::begin; + using std::end; + // we could avoid the assignment, but this might require a for loop, which + // might be less efficient than the container constructor for some containers (would it?) + obj = CompatibleObjectType(begin(*inner_object), end(*inner_object)); +} + +// overload for arithmetic types, not chosen for basic_json template arguments (BooleanType, etc..) +// +// note: Is it really necessary to provide explicit overloads for boolean_t etc.. +// in case of a custom BooleanType which is not an arithmetic type? +template < + typename BasicJsonType, typename ArithmeticType, + enable_if_t < + std::is_arithmetic::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value, + int > = 0 > +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + val = static_cast( + *j.template get_ptr()); + break; + case value_t::number_integer: + val = static_cast( + *j.template get_ptr()); + break; + case value_t::number_float: + val = static_cast( + *j.template get_ptr()); + break; + case value_t::boolean: + val = static_cast( + *j.template get_ptr()); + break; + default: + JSON_THROW( + std::domain_error("type must be number, but is " + j.type_name())); + } +} + +struct to_json_fn +{ + template + auto call(BasicJsonType& j, T&& val, priority_tag<1>) const + noexcept(noexcept(to_json(j, std::forward(val)))) + -> decltype(to_json(j, std::forward(val)), + void()) + { + return to_json(j, std::forward(val)); + } + + template + void call(BasicJsonType&, T&&, priority_tag<0>) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, "to_json method in T's namespace can not be called"); + } + public: - static constexpr bool value = - std::is_integral()))>::value; + template + void operator()(BasicJsonType& j, T&& val) const + noexcept(noexcept(std::declval().call(j, std::forward(val), priority_tag<1> {}))) + { + return call(j, std::forward(val), priority_tag<1> {}); + } +}; + +struct from_json_fn +{ + private: + template + auto call(const BasicJsonType& j, T& val, priority_tag<1>) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } + + template + void call(const BasicJsonType&, T&, priority_tag<0>) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, "from_json method in T's namespace can not be called"); + } + + public: + template + void operator()(const BasicJsonType& j, T& val) const + noexcept(noexcept(std::declval().call(j, val, priority_tag<1> {}))) + { + return call(j, val, priority_tag<1> {}); + } +}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; + +/*! +@brief helper class to create locales with decimal point + +This struct is used a default locale during the JSON serialization. JSON +requires the decimal point to be `.`, so this function overloads the +`do_decimal_point()` function to return `.`. This function is called by +float-to-string conversions to retrieve the decimal separator between integer +and fractional parts. + +@sa https://github.com/nlohmann/json/issues/51#issuecomment-86869315 +@since version 2.0.0 +*/ +struct DecimalSeparator : std::numpunct +{ + char do_decimal_point() const + { + return '.'; + } +}; +} + +namespace +{ +constexpr const auto& to_json = detail::static_const::value; +constexpr const auto& from_json = detail::static_const::value; +} + +// default JSONSerializer template argument, doesn't care about template argument +// will use ADL for serialization +template +struct adl_serializer +{ + template + static void from_json(BasicJsonType&& j, T& val) noexcept(noexcept(::nlohmann::from_json(std::forward(j), val))) + { + ::nlohmann::from_json(std::forward(j), val); + } + + template + static void to_json(BasicJsonType& j, T&& val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward(val)))) + { + ::nlohmann::to_json(j, std::forward(val)); + } }; -} // namespace - /*! @brief a class to store JSON values @@ -226,21 +999,26 @@ template < class NumberIntegerType = std::int64_t, class NumberUnsignedType = std::uint64_t, class NumberFloatType = double, - template class AllocatorType = std::allocator + template class AllocatorType = std::allocator, + template class JSONSerializer = adl_serializer > class basic_json { private: + template friend struct detail::external_constructor; /// workaround type for MSVC using basic_json_t = basic_json; + AllocatorType, JSONSerializer>; public: + using value_t = detail::value_t; // forward declarations template class iter_impl; template class json_reverse_iterator; class json_pointer; + template + using json_serializer = JSONSerializer; ///////////////////// // container types // @@ -787,47 +1565,6 @@ class basic_json /// @} - - /////////////////////////// - // JSON type enumeration // - /////////////////////////// - - /*! - @brief the JSON type enumeration - - 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() (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 - */ - enum class value_t : uint8_t - { - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - 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 - }; - - private: /// helper for exception-safe object creation @@ -1090,549 +1827,94 @@ class basic_json // constructors // ////////////////// - /// @name constructors and destructors - /// Constructors of class @ref basic_json, copy/move constructor, copy - /// assignment, static functions creating objects, and the destructor. - /// @{ - - /*! - @brief create an empty value with a given type - - Create an empty JSON value with a given type. The value will be default - initialized with an empty value which depends on the type: - - Value type | initial value - ----------- | ------------- - null | `null` - boolean | `false` - string | `""` - number | `0` - object | `{}` - array | `[]` - - @param[in] value_type the type of the value to create - - @complexity Constant. - - @throw std::bad_alloc if allocation for object, array, or string value - fails - - @liveexample{The following code shows the constructor for different @ref - value_t values,basic_json__value_t} - - @sa @ref basic_json(std::nullptr_t) -- create a `null` value - @sa @ref basic_json(boolean_t value) -- create a boolean value - @sa @ref basic_json(const string_t&) -- create a string value - @sa @ref basic_json(const object_t&) -- create a object value - @sa @ref basic_json(const array_t&) -- create a array value - @sa @ref basic_json(const number_float_t) -- create a number - (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 - */ - basic_json(const value_t value_type) - : m_type(value_type), m_value(value_type) - { - assert_invariant(); - } - - /*! - @brief create a null object - - Create a `null` JSON value. It either takes a null pointer as parameter - (explicitly creating `null`) or no parameter (implicitly creating `null`). - The passed null pointer itself is not read -- it is only used to choose - the right constructor. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this constructor never throws - exceptions. - - @liveexample{The following code shows the constructor with and without a - null pointer parameter.,basic_json__nullptr_t} - - @since version 1.0.0 - */ - basic_json(std::nullptr_t = nullptr) noexcept - : basic_json(value_t::null) - { - assert_invariant(); - } - - /*! - @brief create an object (explicit) - - Create an object JSON value with a given content. - - @param[in] val a value for the object - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for object value fails - - @liveexample{The following code shows the constructor with an @ref - object_t parameter.,basic_json__object_t} - - @sa @ref basic_json(const CompatibleObjectType&) -- create an object value - from a compatible STL container - - @since version 1.0.0 - */ - basic_json(const object_t& val) - : m_type(value_t::object), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an object (implicit) - - Create an object JSON value with a given content. This constructor allows - any type @a CompatibleObjectType that can be used to construct values of - type @ref object_t. - - @tparam CompatibleObjectType An object type whose `key_type` and - `value_type` is compatible to @ref object_t. Examples include `std::map`, - `std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with - a `key_type` of `std::string`, and a `value_type` from which a @ref - basic_json value can be constructed. - - @param[in] val a value for the object - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for object value fails - - @liveexample{The following code shows the constructor with several - compatible object type parameters.,basic_json__CompatibleObjectType} - - @sa @ref basic_json(const object_t&) -- create an object value - - @since version 1.0.0 - */ - template::value and - std::is_constructible::value, int>::type = 0> - basic_json(const CompatibleObjectType& val) - : m_type(value_t::object) - { - using std::begin; - using std::end; - m_value.object = create(begin(val), end(val)); - assert_invariant(); - } - - /*! - @brief create an array (explicit) - - Create an array JSON value with a given content. - - @param[in] val a value for the array - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for array value fails - - @liveexample{The following code shows the constructor with an @ref array_t - parameter.,basic_json__array_t} - - @sa @ref basic_json(const CompatibleArrayType&) -- create an array value - from a compatible STL containers - - @since version 1.0.0 - */ - basic_json(const array_t& val) - : m_type(value_t::array), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an array (implicit) - - Create an array JSON value with a given content. This constructor allows - any type @a CompatibleArrayType that can be used to construct values of - type @ref array_t. - - @tparam CompatibleArrayType An object type whose `value_type` is - compatible to @ref array_t. Examples include `std::vector`, `std::deque`, - `std::list`, `std::forward_list`, `std::array`, `std::set`, - `std::unordered_set`, `std::multiset`, and `unordered_multiset` with a - `value_type` from which a @ref basic_json value can be constructed. - - @param[in] val a value for the array - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for array value fails - - @liveexample{The following code shows the constructor with several - compatible array type parameters.,basic_json__CompatibleArrayType} - - @sa @ref basic_json(const array_t&) -- create an array value - - @since version 1.0.0 - */ - template::value and - not std::is_same::value and - not std::is_same::value and - not std::is_same::value and - not std::is_same::value and - not std::is_same::value and - std::is_constructible::value, int>::type = 0> - basic_json(const CompatibleArrayType& val) - : m_type(value_t::array) - { - using std::begin; - using std::end; - m_value.array = create(begin(val), end(val)); - assert_invariant(); - } - - /*! - @brief create a string (explicit) - - Create an string JSON value with a given content. - - @param[in] val a value for the string - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for string value fails - - @liveexample{The following code shows the constructor with an @ref - string_t parameter.,basic_json__string_t} - - @sa @ref basic_json(const typename string_t::value_type*) -- create a - string value from a character pointer - @sa @ref basic_json(const CompatibleStringType&) -- create a string value - from a compatible string container - - @since version 1.0.0 - */ - basic_json(const string_t& val) - : m_type(value_t::string), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create a string (explicit) - - Create a string JSON value with a given content. - - @param[in] val a literal value for the string - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for string value fails - - @liveexample{The following code shows the constructor with string literal - parameter.,basic_json__string_t_value_type} - - @sa @ref basic_json(const string_t&) -- create a string value - @sa @ref basic_json(const CompatibleStringType&) -- create a string value - from a compatible string container - - @since version 1.0.0 - */ - basic_json(const typename string_t::value_type* val) - : basic_json(string_t(val)) - { - assert_invariant(); - } - - /*! - @brief create a string (implicit) - - Create a string JSON value with a given content. - - @param[in] val a value for the string - - @tparam CompatibleStringType an string type which is compatible to @ref - string_t, for instance `std::string`. - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for string value fails - - @liveexample{The following code shows the construction of a string value - from a compatible type.,basic_json__CompatibleStringType} - - @sa @ref basic_json(const string_t&) -- create a string value - @sa @ref basic_json(const typename string_t::value_type*) -- create a - string value from a character pointer - - @since version 1.0.0 - */ - template::value, int>::type = 0> - basic_json(const CompatibleStringType& val) - : basic_json(string_t(val)) - { - assert_invariant(); - } - - /*! - @brief create a boolean (explicit) - - Creates a JSON boolean type from a given value. - - @param[in] val a boolean value to store - - @complexity Constant. - - @liveexample{The example below demonstrates boolean - values.,basic_json__boolean_t} - - @since version 1.0.0 - */ - basic_json(boolean_t val) noexcept - : m_type(value_t::boolean), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an integer number (explicit) - - Create an integer number JSON value with a given content. - - @tparam T A helper type to remove this function via SFINAE in case @ref - number_integer_t is the same as `int`. In this case, this constructor - would have the same signature as @ref basic_json(const int value). Note - the helper type @a T is not visible in this constructor's interface. - - @param[in] val an integer to create a JSON number from - - @complexity Constant. - - @liveexample{The example below shows the construction of an integer - number value.,basic_json__number_integer_t} - - @sa @ref basic_json(const int) -- create a number value (integer) - @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number - value (integer) from a compatible number type - - @since version 1.0.0 - */ - template::value) and - std::is_same::value, int>::type = 0> - basic_json(const number_integer_t val) noexcept - : m_type(value_t::number_integer), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an integer number from an enum type (explicit) - - Create an integer number JSON value with a given content. - - @param[in] val an integer to create a JSON number from - - @note This constructor allows to pass enums directly to a constructor. As - C++ has no way of specifying the type of an anonymous enum explicitly, we - can only rely on the fact that such values implicitly convert to int. As - int may already be the same type of number_integer_t, we may need to - switch off the constructor @ref basic_json(const number_integer_t). - - @complexity Constant. - - @liveexample{The example below shows the construction of an integer - number value from an anonymous enum.,basic_json__const_int} - - @sa @ref basic_json(const number_integer_t) -- create a number value - (integer) - @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number - value (integer) from a compatible number type - - @since version 1.0.0 - */ - basic_json(const int val) noexcept - : m_type(value_t::number_integer), - m_value(static_cast(val)) - { - assert_invariant(); - } - - /*! - @brief create an integer number (implicit) - - Create an integer number JSON value with a given content. This constructor - allows any type @a CompatibleNumberIntegerType that can be used to - construct values of type @ref number_integer_t. - - @tparam CompatibleNumberIntegerType An integer type which is compatible to - @ref number_integer_t. Examples include the types `int`, `int32_t`, - `long`, and `short`. - - @param[in] val an integer to create a JSON number from - - @complexity Constant. - - @liveexample{The example below shows the construction of several integer - number values from compatible - types.,basic_json__CompatibleIntegerNumberType} - - @sa @ref basic_json(const number_integer_t) -- create a number value - (integer) - @sa @ref basic_json(const int) -- create a number value (integer) - - @since version 1.0.0 - */ - template::value and - 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)) - { - assert_invariant(); - } - - /*! - @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) noexcept - : m_type(value_t::number_unsigned), m_value(val) - { - assert_invariant(); - } + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ /*! - @brief create an unsigned number (implicit) + @brief create an empty value with a given type - Create an unsigned number JSON value with a given content. This - constructor allows any type @a CompatibleNumberUnsignedType that can be - used to construct values of type @ref number_unsigned_t. + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: - @tparam CompatibleNumberUnsignedType An integer type which is compatible - to @ref number_unsigned_t. Examples may include the types `unsigned int`, - `uint32_t`, or `unsigned short`. + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` - @param[in] val an unsigned integer to create a JSON number from + @param[in] value_type the type of the value to create @complexity Constant. - @sa @ref basic_json(const number_unsigned_t) -- create a number value - (unsigned) + @throw std::bad_alloc if allocation for object, array, or string value + fails - @since version 2.0.0 + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @since version 1.0.0 */ - template::value and - std::numeric_limits::is_integer and - not 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)) + basic_json(const value_t value_type) + : m_type(value_type), m_value(value_type) { assert_invariant(); } /*! - @brief create a floating-point number (explicit) - - Create a floating-point number JSON value with a given content. - - @param[in] val a floating-point value to create a JSON number from + @brief create a null object - @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 - 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. + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. @complexity Constant. - @liveexample{The following example creates several floating-point - values.,basic_json__number_float_t} + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. - @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number - value (floating-point) from a compatible number type + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} @since version 1.0.0 */ - basic_json(const number_float_t val) noexcept - : m_type(value_t::number_float), m_value(val) + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) { - // replace infinity and NAN by null - if (not std::isfinite(val)) - { - m_type = value_t::null; - m_value = json_value(); - } - assert_invariant(); } /*! - @brief create an floating-point number (implicit) - - Create an floating-point number JSON value with a given content. This - constructor allows any type @a CompatibleNumberFloatType that can be used - to construct values of type @ref number_float_t. + @brief forwards the parameter to json_serializer::to_json method (U = uncvref_t) - @tparam CompatibleNumberFloatType A floating-point type which is - compatible to @ref number_float_t. Examples may include the types `float` - or `double`. - - @param[in] val a floating-point to create a JSON number from - - @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 - 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. - - @complexity Constant. + this constructor is chosen if: + - T is not derived from std::istream + - T is not @ref basic_json (to avoid hijacking copy/move constructors) + - T is not a @ref basic_json nested type (@ref json_pointer, @ref iterator, etc ...) + - @ref json_serializer has a to_json(basic_json_t&, T&&) method - @liveexample{The example below shows the construction of several - floating-point number values from compatible - types.,basic_json__CompatibleNumberFloatType} + @param[in] val the value to be forwarded - @sa @ref basic_json(const number_float_t) -- create a number value - (floating-point) + @throw what json_serializer::to_json throws - @since version 1.0.0 + @since version 2.1.0 */ - template::value and - std::is_floating_point::value>::type> - basic_json(const CompatibleNumberFloatType val) noexcept - : basic_json(number_float_t(val)) + template , + detail::enable_if_t::value and + not std::is_same::value and + not detail::is_basic_json_nested_type< + basic_json_t, U>::value and + detail::has_to_json::value, + int> = 0> + basic_json(T && val) noexcept(noexcept(JSONSerializer::to_json( + std::declval(), std::forward(val)))) { - assert_invariant(); + JSONSerializer::to_json(*this, std::forward(val)); } /*! @@ -2651,146 +2933,6 @@ class basic_json /// @} private: - ////////////////// - // value access // - ////////////////// - - /// get an object (explicit) - template::value and - std::is_convertible::value, int>::type = 0> - T get_impl(T* /*unused*/) const - { - if (is_object()) - { - return T(m_value.object->begin(), m_value.object->end()); - } - - JSON_THROW(std::domain_error("type must be object, but is " + type_name())); - } - - /// get an object (explicit) - object_t get_impl(object_t* /*unused*/) const - { - if (is_object()) - { - return *(m_value.object); - } - - JSON_THROW(std::domain_error("type must be object, but is " + type_name())); - } - - /// get an array (explicit) - template::value and - not std::is_same::value and - not std::is_arithmetic::value and - not std::is_convertible::value and - not has_mapped_type::value, int>::type = 0> - T get_impl(T* /*unused*/) const - { - if (is_array()) - { - T to_vector; - std::transform(m_value.array->begin(), m_value.array->end(), - std::inserter(to_vector, to_vector.end()), [](basic_json i) - { - return i.get(); - }); - return to_vector; - } - - JSON_THROW(std::domain_error("type must be array, but is " + type_name())); - } - - /// get an array (explicit) - template::value and - not std::is_same::value, int>::type = 0> - std::vector get_impl(std::vector* /*unused*/) const - { - if (is_array()) - { - std::vector to_vector; - to_vector.reserve(m_value.array->size()); - std::transform(m_value.array->begin(), m_value.array->end(), - std::inserter(to_vector, to_vector.end()), [](basic_json i) - { - return i.get(); - }); - return to_vector; - } - - JSON_THROW(std::domain_error("type must be array, but is " + type_name())); - } - - /// get an array (explicit) - template::value and - not has_mapped_type::value, int>::type = 0> - T get_impl(T* /*unused*/) const - { - if (is_array()) - { - return T(m_value.array->begin(), m_value.array->end()); - } - - JSON_THROW(std::domain_error("type must be array, but is " + type_name())); - } - - /// get an array (explicit) - array_t get_impl(array_t* /*unused*/) const - { - if (is_array()) - { - return *(m_value.array); - } - - JSON_THROW(std::domain_error("type must be array, but is " + type_name())); - } - - /// get a string (explicit) - template::value, int>::type = 0> - T get_impl(T* /*unused*/) const - { - if (is_string()) - { - return *m_value.string; - } - - JSON_THROW(std::domain_error("type must be string, but is " + type_name())); - } - - /// get a number (explicit) - template::value, int>::type = 0> - T get_impl(T* /*unused*/) const - { - switch (m_type) - { - case value_t::number_integer: - { - return static_cast(m_value.number_integer); - } - - case value_t::number_unsigned: - { - return static_cast(m_value.number_unsigned); - } - - case value_t::number_float: - { - return static_cast(m_value.number_float); - } - - default: - { - JSON_THROW(std::domain_error("type must be number, but is " + type_name())); - } - } - } - /// get a boolean (explicit) boolean_t get_impl(boolean_t* /*unused*/) const { @@ -2918,49 +3060,92 @@ class basic_json } public: + /*! + @brief get special-case overload - /// @name value access - /// Direct access to the stored value of a JSON value. - /// @{ + This overloads avoids a lot of template boilerplate, it can be seen as the identity method + + @tparam T type; T == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template < + typename T, + detail::enable_if_t::type, + basic_json_t>::value, + int> = 0 > + basic_json get() const + { + return *this; + } /*! - @brief get a value (explicit) + @brief get overload for CopyConstructible and DefaultConstructible types + construct a default U value, and call @ref json_serializer from_json method with it - Explicit type conversion between the JSON value and a compatible value. + This overloads is chosen if: + - U is not @ref basic_json + - @ref json_serializer has a from_json method of the form: void from_json(const @ref basic_json&, U&) + - @ref json_serializer does not have a from_json method of the form: U from_json(const @ref basic_json&); - @tparam ValueType non-pointer type compatible to the JSON value, for - instance `int` for JSON integer numbers, `bool` for JSON booleans, or - `std::vector` types for JSON arrays + @return a value of type U - @return copy of the JSON value, converted to type @a ValueType + @throw what json_serializer from_json method throws - @throw std::domain_error in case passed type @a ValueType is incompatible - to JSON; example: `"type must be object, but is null"` + @since version 2.1.0 + */ + template < + typename T, + typename U = detail::uncvref_t, + detail::enable_if_t < + not std::is_same::value and + detail::has_from_json::value and + not detail::has_non_default_from_json::value, + int > = 0 > + U get() const noexcept(noexcept(JSONSerializer::from_json( + std::declval(), std::declval()))) + { + // we cannot static_assert on T being non-const, because there is support + // for get(), which is why we still need the uncvref + static_assert(not std::is_reference::value, "get cannot be used with reference types, you might want to use get_ref"); + static_assert(std::is_default_constructible::value, + "Types must be DefaultConstructible when used with get"); + U ret; + JSONSerializer::from_json(*this, ret); + return ret; + } - @complexity Linear in the size of the JSON value. + /*! + @brief get overload for types than cannot be default constructed or copy constructed - @liveexample{The example below shows several conversions from JSON values - to other types. There a few things to note: (1) Floating-point numbers can - be converted to integers\, (2) A JSON array can be converted to a standard - `std::vector`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`.,get__ValueType_const} + If @ref json_serializer has both overloads of from_json, this one is chosen - @internal - The idea of using a casted null pointer to choose the correct - implementation is from . - @endinternal + This overloads is chosen if: + - U is not @ref basic_json + - @ref json_serializer has a from_json method of the form: U from_json(const @ref basic_json&); - @sa @ref operator ValueType() const for implicit conversion - @sa @ref get() for pointer-member access + @return a value of type U - @since version 1.0.0 + @throw what json_serializer from_json method throws + + @since version 2.1.0 */ - template::value, int>::type = 0> - ValueType get() const + template < + typename T, + typename U = detail::uncvref_t, + detail::enable_if_t::value and + detail::has_non_default_from_json::value, + int> = 0 > + U get() const noexcept(noexcept(JSONSerializer::from_json(std::declval()))) { - return get_impl(static_cast(nullptr)); + static_assert(not std::is_reference::value, "get cannot be used with reference types, you might want to use get_ref"); + return JSONSerializer::from_json(*this); } /*! @@ -5528,47 +5713,6 @@ class basic_json /// @} - - ////////////////////////////////////////// - // lexicographical comparison operators // - ////////////////////////////////////////// - - /// @name lexicographical comparison operators - /// @{ - - private: - /*! - @brief comparison operator for JSON types - - Returns an ordering that is similar to Python: - - order: null < boolean < number < object < array < string - - furthermore, each type is not smaller than itself - - @since version 1.0.0 - */ - friend bool operator<(const value_t lhs, const value_t rhs) noexcept - { - static constexpr std::array order = {{ - 0, // null - 3, // object - 4, // array - 5, // string - 1, // boolean - 2, // integer - 2, // unsigned - 2, // float - } - }; - - // discarded values are not comparable - if (lhs == value_t::discarded or rhs == value_t::discarded) - { - return false; - } - - return order[static_cast(lhs)] < order[static_cast(rhs)]; - } - public: /*! @brief comparison: equal @@ -7767,7 +7911,6 @@ class basic_json /// @} - private: /////////////////////////// // convenience functions // /////////////////////////// @@ -7782,29 +7925,36 @@ class basic_json @complexity Constant. + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,typename} + @since version 1.0.0 */ std::string type_name() const { - switch (m_type) { - case value_t::null: - return "null"; - case value_t::object: - return "object"; - case value_t::array: - return "array"; - case value_t::string: - return "string"; - case value_t::boolean: - return "boolean"; - case value_t::discarded: - return "discarded"; - default: - return "number"; + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } } + } + private: /*! @brief calculates the extra space to escape a JSON string @@ -8152,6 +8302,11 @@ class basic_json class primitive_iterator_t { public: + + difference_type get_value() const noexcept + { + return m_it; + } /// set iterator to a defined beginning void set_begin() noexcept { @@ -8176,16 +8331,87 @@ class basic_json return (m_it == end_value); } - /// return reference to the value to change and compare - operator difference_type& () noexcept + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { - return m_it; + return lhs.m_it == rhs.m_it; } - /// return value to compare - constexpr operator difference_type () const noexcept + friend constexpr bool operator!=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { - return m_it; + return !(lhs == rhs); + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + friend constexpr bool operator<=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it <= rhs.m_it; + } + + friend constexpr bool operator>(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it > rhs.m_it; + } + + friend constexpr bool operator>=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it >= rhs.m_it; + } + + primitive_iterator_t operator+(difference_type i) + { + auto result = *this; + result += i; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + friend std::ostream& operator<<(std::ostream& os, primitive_iterator_t it) + { + return os << it.m_it; + } + + primitive_iterator_t& operator++() + { + ++m_it; + return *this; + } + + primitive_iterator_t& operator++(int) + { + m_it++; + return *this; + } + + primitive_iterator_t& operator--() + { + --m_it; + return *this; + } + + primitive_iterator_t& operator--(int) + { + m_it--; + return *this; + } + + primitive_iterator_t& operator+=(difference_type n) + { + m_it += n; + return *this; + } + + primitive_iterator_t& operator-=(difference_type n) + { + m_it -= n; + return *this; } private: @@ -8890,7 +9116,7 @@ class basic_json default: { - if (m_it.primitive_iterator == -n) + if (m_it.primitive_iterator.get_value() == -n) { return *m_object; } @@ -11543,6 +11769,18 @@ class basic_json } private: + friend bool operator==(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return lhs.reference_tokens == rhs.reference_tokens; + } + + friend bool operator!=(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return !(lhs == rhs); + } + /// the reference tokens std::vector reference_tokens {}; }; @@ -12203,7 +12441,6 @@ class basic_json /// @} }; - ///////////// // presets // ///////////// diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 886b9dde5d..8c670c909c 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -39,6 +39,7 @@ SOFTWARE. #include // int64_t, uint64_t #include // strtod, strtof, strtold, strtoul #include // strlen +#include // forward_list #include // function, hash, less #include // initializer_list #include // setw @@ -106,39 +107,811 @@ SOFTWARE. */ namespace nlohmann { +// TODO update this doc +/*! +@brief unnamed namespace with internal helper functions +@since version 1.0.0 +*/ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// /*! -@brief unnamed namespace with internal helper functions +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used +to distinguish the stored values, and the functions @ref basic_json::is_null(), @ref +basic_json::is_object(), @ref basic_json::is_array(), @ref basic_json::is_string(), @ref basic_json::is_boolean(), @ref +basic_json::is_number() (with @ref basic_json::is_number_integer(), @ref basic_json::is_number_unsigned(), and +@ref basic_json::is_number_float()), @ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::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 basic_json::number_unsigned_t is used for unsigned +integers, @ref basic_json::number_integer_t is used for signed integers, and @ref +basic_json::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::basic_json(const value_t value_type) -- create a JSON value with +the default value for a given type + @since version 1.0.0 */ -namespace +enum class value_t : uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + 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 +}; + +////////////////////////////////////////// +// lexicographical comparison operators // +////////////////////////////////////////// + +/// @name lexicographical comparison operators +/// @{ +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string +- furthermore, each type is not smaller than itself + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array order = {{ + 0, // null + 3, // object + 4, // array + 5, // string + 1, // boolean + 2, // integer + 2, // unsigned + 2, // float + } + }; + + // discarded values are not comparable + if (lhs == value_t::discarded or rhs == value_t::discarded) + { + return false; + } + + return order[static_cast(lhs)] < + order[static_cast(rhs)]; +} + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// Taken from http://stackoverflow.com/questions/26936640/how-to-implement-is-enum-class-type-trait +template +using is_unscoped_enum = + std::integral_constant::value and + std::is_enum::value>; + +// Implementation of 2 C++17 constructs: conjunction, negation. +// This is needed to avoid evaluating all the traits in a condition +// +// For example: not std::is_same::value and has_value_type::value +// will not compile when T = void (on MSVC at least) +// Whereas conjunction>, has_value_type>::value +// will stop evaluating if negation<...>::value == false +// +// Please note that those constructs must be used with caution, since symbols can +// become very long quickly (which can slow down compilation and cause MSVC internal compiler errors) +// Only use it when you have too (see example ahead) +template struct conjunction : std::true_type {}; +template struct conjunction : B1 {}; +template +struct conjunction +: std::conditional, B1>::type {}; + +template struct negation : std::integral_constant < bool, !B::value > {}; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; + +template <> struct priority_tag<0> {}; + +// This is an experiment. I need this to move constructors out of basic_json. +// I'm sure there is a better way, but this might need a big basic_json refactoring +template struct external_constructor; + +template <> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept + { + j.m_type = value_t::boolean; + j.m_value = b; + j.assert_invariant(); + } +}; + +template <> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) + { + j.m_type = value_t::string; + j.m_value = s; + j.assert_invariant(); + } +}; + +template <> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept + { + // replace infinity and NAN by null + if (not std::isfinite(val)) + j = BasicJsonType{}; + else + { + j.m_type = value_t::number_float; + j.m_value = val; + } + j.assert_invariant(); + } +}; + +template <> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept + { + j.m_type = value_t::number_unsigned; + j.m_value = val; + j.assert_invariant(); + } +}; + +template <> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept + { + j.m_type = value_t::number_integer; + j.m_value = val; + j.assert_invariant(); + } +}; + +template <> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template ::value, + int> = 0> + static void construct(BasicJsonType& j, const CompatibleArrayType& arr) + { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = + j.template create(begin(arr), end(arr)); + j.assert_invariant(); + } +}; + +template <> +struct external_constructor { + template + static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) + { + j.m_type = value_t::object; + j.m_value = obj; + j.assert_invariant(); + } + + template ::value, + int> = 0> + static void construct(BasicJsonType& j, const CompatibleObjectType& obj) + { + using std::begin; + using std::end; + + j.m_type = value_t::object; + j.m_value.object = + j.template create(begin(obj), end(obj)); + j.assert_invariant(); + } +}; + /*! @brief Helper to determine whether there's a key_type for T. -Thus helper is used to tell associative containers apart from other containers +This helper is used to tell associative containers apart from other containers such as sequence containers. For instance, `std::map` passes the test as it contains a `mapped_type`, whereas `std::vector` fails the test. @sa http://stackoverflow.com/a/7728728/266378 @since version 1.0.0, overworked in version 2.0.6 */ -template -struct has_mapped_type +#define NLOHMANN_JSON_HAS_HELPER(type) \ + template struct has_##type { \ + private: \ + template \ + static int detect(U &&); \ + static void detect(...); \ + public: \ + static constexpr bool value = \ + std::is_integral()))>::value; \ + } + +NLOHMANN_JSON_HAS_HELPER(mapped_type); +NLOHMANN_JSON_HAS_HELPER(key_type); +NLOHMANN_JSON_HAS_HELPER(value_type); +NLOHMANN_JSON_HAS_HELPER(iterator); + +#undef NLOHMANN_JSON_HAS_HELPER + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl +{ + static constexpr auto value = + std::is_constructible::value and + std::is_constructible::value; +}; + +template +struct is_compatible_object_type +{ + static auto constexpr value = is_compatible_object_type_impl < + conjunction>, + has_mapped_type, + has_key_type>::value, + typename BasicJsonType::object_t, CompatibleObjectType >::value; +}; + +template +struct is_basic_json_nested_type +{ + static auto constexpr value = std::is_same::value or + std::is_same::value or + std::is_same::value or + std::is_same::value or + std::is_same::value; +}; + +template +struct is_compatible_array_type +{ + // TODO concept Container? + // this might not make VS happy + static auto constexpr value = + conjunction>, + negation>, + negation>, + negation>, + has_value_type, + has_iterator>::value; +}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + std::is_constructible::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type +{ + static constexpr auto + value = is_compatible_integer_type_impl < + std::is_integral::value and + not std::is_same::value, + RealIntegerType, CompatibleNumberIntegerType > ::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json { private: - template + // also check the return type of from_json + template ::from_json( + std::declval(), std::declval()))>::value>> static int detect(U&&); + static void detect(...); + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json +{ + private: + template < + typename U, + typename = enable_if_t::from_json(std::declval()))>::value >> + static int detect(U&&); static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +template +struct has_to_json +{ + private: + template ::to_json( + std::declval(), std::declval()))> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// overloads for basic_json template parameters + +template ::value and + not std::is_same::value, + int> = 0> +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + val = static_cast( + *j.template get_ptr()); + break; + case value_t::number_integer: + val = static_cast( + *j.template get_ptr()); + break; + case value_t::number_float: + val = static_cast( + *j.template get_ptr()); + break; + default: + JSON_THROW( + std::domain_error("type must be number, but is " + j.type_name())); + } +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept +{ + external_constructor::construct(j, b); +} + +template ::value, + int> = 0> +void to_json(BasicJsonType& j, const CompatibleString& s) +{ + external_constructor::construct(j, s); +} + +template ::value, int> = 0> +void to_json(BasicJsonType& j, FloatType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + + +template < + typename BasicJsonType, typename CompatibleNumberUnsignedType, + enable_if_t::value, + int> = 0 > +void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template < + typename BasicJsonType, typename CompatibleNumberIntegerType, + enable_if_t::value, + int> = 0 > +void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template ::value, int> = 0> +void to_json(BasicJsonType& j, UnscopedEnumType e) noexcept +{ + external_constructor::construct(j, e); +} + +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < + is_compatible_array_type::value or + std::is_same::value, + int > = 0 > +void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +{ + external_constructor::construct(j, arr); +} + +template < + typename BasicJsonType, typename CompatibleObjectType, + enable_if_t::value, + int> = 0 > +void to_json(BasicJsonType& j, const CompatibleObjectType& arr) +{ + external_constructor::construct(j, arr); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (!j.is_boolean()) + { + JSON_THROW(std::domain_error("type must be boolean, but is " + j.type_name())); + } + b = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (!j.is_string()) + { + JSON_THROW(std::domain_error("type must be string, but is " + j.type_name())); + } + s = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +template ::value, int> = 0> +void from_json(const BasicJsonType& j, UnscopedEnumType& e) +{ + typename std::underlying_type::type val = e; + get_arithmetic_value(j, val); + e = static_cast(val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) +{ + if (!j.is_array()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + arr = *j.template get_ptr(); +} + +// forward_list doesn't have an insert method, TODO find a way to avoid including forward_list +template +void from_json(const BasicJsonType& j, std::forward_list& l) +{ + // do not perform the check when user wants to retrieve jsons + // (except when it's null.. ?) + if (j.is_null()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + if (not std::is_same::value) + { + if (!j.is_array()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + } + for (auto it = j.rbegin(), end = j.rend(); it != end; ++it) + { + l.push_front(it->template get()); + } +} + +template +void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0>) +{ + using std::begin; + using std::end; + + std::transform( + j.begin(), j.end(), std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json method when + // value_type is BasicJsonType + return i.template get(); + }); +} + +template +auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1>) +-> decltype( + arr.reserve(std::declval()), + void()) +{ + using std::begin; + using std::end; + + arr.reserve(j.size()); + std::transform( + j.begin(), j.end(), std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json method when + // value_type is BasicJsonType + return i.template get(); + }); +} + +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t::value and + not std::is_same::value, + int> = 0 > +void from_json(const BasicJsonType& j, CompatibleArrayType& arr) +{ + if (j.is_null()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + // when T == BasicJsonType, do not check if value_t is correct + if (not std::is_same::value) + { + if (!j.is_array()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + } + from_json_array_impl(j, arr, priority_tag<1> {}); +} + + +template < + typename BasicJsonType, typename CompatibleObjectType, + enable_if_t::value, + int> = 0 > +void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +{ + if (!j.is_object()) + { + JSON_THROW(std::domain_error("type must be object, but is " + j.type_name())); + } + + auto inner_object = j.template get_ptr(); + using std::begin; + using std::end; + // we could avoid the assignment, but this might require a for loop, which + // might be less efficient than the container constructor for some containers (would it?) + obj = CompatibleObjectType(begin(*inner_object), end(*inner_object)); +} + +// overload for arithmetic types, not chosen for basic_json template arguments (BooleanType, etc..) +// +// note: Is it really necessary to provide explicit overloads for boolean_t etc.. +// in case of a custom BooleanType which is not an arithmetic type? +template < + typename BasicJsonType, typename ArithmeticType, + enable_if_t < + std::is_arithmetic::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value, + int > = 0 > +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + val = static_cast( + *j.template get_ptr()); + break; + case value_t::number_integer: + val = static_cast( + *j.template get_ptr()); + break; + case value_t::number_float: + val = static_cast( + *j.template get_ptr()); + break; + case value_t::boolean: + val = static_cast( + *j.template get_ptr()); + break; + default: + JSON_THROW( + std::domain_error("type must be number, but is " + j.type_name())); + } +} + +struct to_json_fn +{ + template + auto call(BasicJsonType& j, T&& val, priority_tag<1>) const + noexcept(noexcept(to_json(j, std::forward(val)))) + -> decltype(to_json(j, std::forward(val)), + void()) + { + return to_json(j, std::forward(val)); + } + + template + void call(BasicJsonType&, T&&, priority_tag<0>) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, "to_json method in T's namespace can not be called"); + } + public: - static constexpr bool value = - std::is_integral()))>::value; + template + void operator()(BasicJsonType& j, T&& val) const + noexcept(noexcept(std::declval().call(j, std::forward(val), priority_tag<1> {}))) + { + return call(j, std::forward(val), priority_tag<1> {}); + } +}; + +struct from_json_fn +{ + private: + template + auto call(const BasicJsonType& j, T& val, priority_tag<1>) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } + + template + void call(const BasicJsonType&, T&, priority_tag<0>) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, "from_json method in T's namespace can not be called"); + } + + public: + template + void operator()(const BasicJsonType& j, T& val) const + noexcept(noexcept(std::declval().call(j, val, priority_tag<1> {}))) + { + return call(j, val, priority_tag<1> {}); + } +}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; + +/*! +@brief helper class to create locales with decimal point + +This struct is used a default locale during the JSON serialization. JSON +requires the decimal point to be `.`, so this function overloads the +`do_decimal_point()` function to return `.`. This function is called by +float-to-string conversions to retrieve the decimal separator between integer +and fractional parts. + +@sa https://github.com/nlohmann/json/issues/51#issuecomment-86869315 +@since version 2.0.0 +*/ +struct DecimalSeparator : std::numpunct +{ + char do_decimal_point() const + { + return '.'; + } +}; +} + +namespace +{ +constexpr const auto& to_json = detail::static_const::value; +constexpr const auto& from_json = detail::static_const::value; +} + +// default JSONSerializer template argument, doesn't care about template argument +// will use ADL for serialization +template +struct adl_serializer +{ + template + static void from_json(BasicJsonType&& j, T& val) noexcept(noexcept(::nlohmann::from_json(std::forward(j), val))) + { + ::nlohmann::from_json(std::forward(j), val); + } + + template + static void to_json(BasicJsonType& j, T&& val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward(val)))) + { + ::nlohmann::to_json(j, std::forward(val)); + } }; -} // namespace - /*! @brief a class to store JSON values @@ -226,21 +999,26 @@ template < class NumberIntegerType = std::int64_t, class NumberUnsignedType = std::uint64_t, class NumberFloatType = double, - template class AllocatorType = std::allocator + template class AllocatorType = std::allocator, + template class JSONSerializer = adl_serializer > class basic_json { private: + template friend struct detail::external_constructor; /// workaround type for MSVC using basic_json_t = basic_json; + AllocatorType, JSONSerializer>; public: + using value_t = detail::value_t; // forward declarations template class iter_impl; template class json_reverse_iterator; class json_pointer; + template + using json_serializer = JSONSerializer; ///////////////////// // container types // @@ -787,47 +1565,6 @@ class basic_json /// @} - - /////////////////////////// - // JSON type enumeration // - /////////////////////////// - - /*! - @brief the JSON type enumeration - - 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() (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 - */ - enum class value_t : uint8_t - { - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - 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 - }; - - private: /// helper for exception-safe object creation @@ -1090,549 +1827,94 @@ class basic_json // constructors // ////////////////// - /// @name constructors and destructors - /// Constructors of class @ref basic_json, copy/move constructor, copy - /// assignment, static functions creating objects, and the destructor. - /// @{ - - /*! - @brief create an empty value with a given type - - Create an empty JSON value with a given type. The value will be default - initialized with an empty value which depends on the type: - - Value type | initial value - ----------- | ------------- - null | `null` - boolean | `false` - string | `""` - number | `0` - object | `{}` - array | `[]` - - @param[in] value_type the type of the value to create - - @complexity Constant. - - @throw std::bad_alloc if allocation for object, array, or string value - fails - - @liveexample{The following code shows the constructor for different @ref - value_t values,basic_json__value_t} - - @sa @ref basic_json(std::nullptr_t) -- create a `null` value - @sa @ref basic_json(boolean_t value) -- create a boolean value - @sa @ref basic_json(const string_t&) -- create a string value - @sa @ref basic_json(const object_t&) -- create a object value - @sa @ref basic_json(const array_t&) -- create a array value - @sa @ref basic_json(const number_float_t) -- create a number - (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 - */ - basic_json(const value_t value_type) - : m_type(value_type), m_value(value_type) - { - assert_invariant(); - } - - /*! - @brief create a null object - - Create a `null` JSON value. It either takes a null pointer as parameter - (explicitly creating `null`) or no parameter (implicitly creating `null`). - The passed null pointer itself is not read -- it is only used to choose - the right constructor. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this constructor never throws - exceptions. - - @liveexample{The following code shows the constructor with and without a - null pointer parameter.,basic_json__nullptr_t} - - @since version 1.0.0 - */ - basic_json(std::nullptr_t = nullptr) noexcept - : basic_json(value_t::null) - { - assert_invariant(); - } - - /*! - @brief create an object (explicit) - - Create an object JSON value with a given content. - - @param[in] val a value for the object - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for object value fails - - @liveexample{The following code shows the constructor with an @ref - object_t parameter.,basic_json__object_t} - - @sa @ref basic_json(const CompatibleObjectType&) -- create an object value - from a compatible STL container - - @since version 1.0.0 - */ - basic_json(const object_t& val) - : m_type(value_t::object), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an object (implicit) - - Create an object JSON value with a given content. This constructor allows - any type @a CompatibleObjectType that can be used to construct values of - type @ref object_t. - - @tparam CompatibleObjectType An object type whose `key_type` and - `value_type` is compatible to @ref object_t. Examples include `std::map`, - `std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with - a `key_type` of `std::string`, and a `value_type` from which a @ref - basic_json value can be constructed. - - @param[in] val a value for the object - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for object value fails - - @liveexample{The following code shows the constructor with several - compatible object type parameters.,basic_json__CompatibleObjectType} - - @sa @ref basic_json(const object_t&) -- create an object value - - @since version 1.0.0 - */ - template::value and - std::is_constructible::value, int>::type = 0> - basic_json(const CompatibleObjectType& val) - : m_type(value_t::object) - { - using std::begin; - using std::end; - m_value.object = create(begin(val), end(val)); - assert_invariant(); - } - - /*! - @brief create an array (explicit) - - Create an array JSON value with a given content. - - @param[in] val a value for the array - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for array value fails - - @liveexample{The following code shows the constructor with an @ref array_t - parameter.,basic_json__array_t} - - @sa @ref basic_json(const CompatibleArrayType&) -- create an array value - from a compatible STL containers - - @since version 1.0.0 - */ - basic_json(const array_t& val) - : m_type(value_t::array), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an array (implicit) - - Create an array JSON value with a given content. This constructor allows - any type @a CompatibleArrayType that can be used to construct values of - type @ref array_t. - - @tparam CompatibleArrayType An object type whose `value_type` is - compatible to @ref array_t. Examples include `std::vector`, `std::deque`, - `std::list`, `std::forward_list`, `std::array`, `std::set`, - `std::unordered_set`, `std::multiset`, and `unordered_multiset` with a - `value_type` from which a @ref basic_json value can be constructed. - - @param[in] val a value for the array - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for array value fails - - @liveexample{The following code shows the constructor with several - compatible array type parameters.,basic_json__CompatibleArrayType} - - @sa @ref basic_json(const array_t&) -- create an array value - - @since version 1.0.0 - */ - template::value and - not std::is_same::value and - not std::is_same::value and - not std::is_same::value and - not std::is_same::value and - not std::is_same::value and - std::is_constructible::value, int>::type = 0> - basic_json(const CompatibleArrayType& val) - : m_type(value_t::array) - { - using std::begin; - using std::end; - m_value.array = create(begin(val), end(val)); - assert_invariant(); - } - - /*! - @brief create a string (explicit) - - Create an string JSON value with a given content. - - @param[in] val a value for the string - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for string value fails - - @liveexample{The following code shows the constructor with an @ref - string_t parameter.,basic_json__string_t} - - @sa @ref basic_json(const typename string_t::value_type*) -- create a - string value from a character pointer - @sa @ref basic_json(const CompatibleStringType&) -- create a string value - from a compatible string container - - @since version 1.0.0 - */ - basic_json(const string_t& val) - : m_type(value_t::string), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create a string (explicit) - - Create a string JSON value with a given content. - - @param[in] val a literal value for the string - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for string value fails - - @liveexample{The following code shows the constructor with string literal - parameter.,basic_json__string_t_value_type} - - @sa @ref basic_json(const string_t&) -- create a string value - @sa @ref basic_json(const CompatibleStringType&) -- create a string value - from a compatible string container - - @since version 1.0.0 - */ - basic_json(const typename string_t::value_type* val) - : basic_json(string_t(val)) - { - assert_invariant(); - } - - /*! - @brief create a string (implicit) - - Create a string JSON value with a given content. - - @param[in] val a value for the string - - @tparam CompatibleStringType an string type which is compatible to @ref - string_t, for instance `std::string`. - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for string value fails - - @liveexample{The following code shows the construction of a string value - from a compatible type.,basic_json__CompatibleStringType} - - @sa @ref basic_json(const string_t&) -- create a string value - @sa @ref basic_json(const typename string_t::value_type*) -- create a - string value from a character pointer - - @since version 1.0.0 - */ - template::value, int>::type = 0> - basic_json(const CompatibleStringType& val) - : basic_json(string_t(val)) - { - assert_invariant(); - } - - /*! - @brief create a boolean (explicit) - - Creates a JSON boolean type from a given value. - - @param[in] val a boolean value to store - - @complexity Constant. - - @liveexample{The example below demonstrates boolean - values.,basic_json__boolean_t} - - @since version 1.0.0 - */ - basic_json(boolean_t val) noexcept - : m_type(value_t::boolean), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an integer number (explicit) - - Create an integer number JSON value with a given content. - - @tparam T A helper type to remove this function via SFINAE in case @ref - number_integer_t is the same as `int`. In this case, this constructor - would have the same signature as @ref basic_json(const int value). Note - the helper type @a T is not visible in this constructor's interface. - - @param[in] val an integer to create a JSON number from - - @complexity Constant. - - @liveexample{The example below shows the construction of an integer - number value.,basic_json__number_integer_t} - - @sa @ref basic_json(const int) -- create a number value (integer) - @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number - value (integer) from a compatible number type - - @since version 1.0.0 - */ - template::value) and - std::is_same::value, int>::type = 0> - basic_json(const number_integer_t val) noexcept - : m_type(value_t::number_integer), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an integer number from an enum type (explicit) - - Create an integer number JSON value with a given content. - - @param[in] val an integer to create a JSON number from - - @note This constructor allows to pass enums directly to a constructor. As - C++ has no way of specifying the type of an anonymous enum explicitly, we - can only rely on the fact that such values implicitly convert to int. As - int may already be the same type of number_integer_t, we may need to - switch off the constructor @ref basic_json(const number_integer_t). - - @complexity Constant. - - @liveexample{The example below shows the construction of an integer - number value from an anonymous enum.,basic_json__const_int} - - @sa @ref basic_json(const number_integer_t) -- create a number value - (integer) - @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number - value (integer) from a compatible number type - - @since version 1.0.0 - */ - basic_json(const int val) noexcept - : m_type(value_t::number_integer), - m_value(static_cast(val)) - { - assert_invariant(); - } - - /*! - @brief create an integer number (implicit) - - Create an integer number JSON value with a given content. This constructor - allows any type @a CompatibleNumberIntegerType that can be used to - construct values of type @ref number_integer_t. - - @tparam CompatibleNumberIntegerType An integer type which is compatible to - @ref number_integer_t. Examples include the types `int`, `int32_t`, - `long`, and `short`. - - @param[in] val an integer to create a JSON number from - - @complexity Constant. - - @liveexample{The example below shows the construction of several integer - number values from compatible - types.,basic_json__CompatibleIntegerNumberType} - - @sa @ref basic_json(const number_integer_t) -- create a number value - (integer) - @sa @ref basic_json(const int) -- create a number value (integer) - - @since version 1.0.0 - */ - template::value and - 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)) - { - assert_invariant(); - } - - /*! - @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) noexcept - : m_type(value_t::number_unsigned), m_value(val) - { - assert_invariant(); - } + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ /*! - @brief create an unsigned number (implicit) + @brief create an empty value with a given type - Create an unsigned number JSON value with a given content. This - constructor allows any type @a CompatibleNumberUnsignedType that can be - used to construct values of type @ref number_unsigned_t. + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: - @tparam CompatibleNumberUnsignedType An integer type which is compatible - to @ref number_unsigned_t. Examples may include the types `unsigned int`, - `uint32_t`, or `unsigned short`. + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` - @param[in] val an unsigned integer to create a JSON number from + @param[in] value_type the type of the value to create @complexity Constant. - @sa @ref basic_json(const number_unsigned_t) -- create a number value - (unsigned) + @throw std::bad_alloc if allocation for object, array, or string value + fails - @since version 2.0.0 + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @since version 1.0.0 */ - template::value and - std::numeric_limits::is_integer and - not 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)) + basic_json(const value_t value_type) + : m_type(value_type), m_value(value_type) { assert_invariant(); } /*! - @brief create a floating-point number (explicit) - - Create a floating-point number JSON value with a given content. - - @param[in] val a floating-point value to create a JSON number from + @brief create a null object - @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 - 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. + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. @complexity Constant. - @liveexample{The following example creates several floating-point - values.,basic_json__number_float_t} + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. - @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number - value (floating-point) from a compatible number type + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} @since version 1.0.0 */ - basic_json(const number_float_t val) noexcept - : m_type(value_t::number_float), m_value(val) + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) { - // replace infinity and NAN by null - if (not std::isfinite(val)) - { - m_type = value_t::null; - m_value = json_value(); - } - assert_invariant(); } /*! - @brief create an floating-point number (implicit) - - Create an floating-point number JSON value with a given content. This - constructor allows any type @a CompatibleNumberFloatType that can be used - to construct values of type @ref number_float_t. + @brief forwards the parameter to json_serializer::to_json method (U = uncvref_t) - @tparam CompatibleNumberFloatType A floating-point type which is - compatible to @ref number_float_t. Examples may include the types `float` - or `double`. - - @param[in] val a floating-point to create a JSON number from - - @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 - 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. - - @complexity Constant. + this constructor is chosen if: + - T is not derived from std::istream + - T is not @ref basic_json (to avoid hijacking copy/move constructors) + - T is not a @ref basic_json nested type (@ref json_pointer, @ref iterator, etc ...) + - @ref json_serializer has a to_json(basic_json_t&, T&&) method - @liveexample{The example below shows the construction of several - floating-point number values from compatible - types.,basic_json__CompatibleNumberFloatType} + @param[in] val the value to be forwarded - @sa @ref basic_json(const number_float_t) -- create a number value - (floating-point) + @throw what json_serializer::to_json throws - @since version 1.0.0 + @since version 2.1.0 */ - template::value and - std::is_floating_point::value>::type> - basic_json(const CompatibleNumberFloatType val) noexcept - : basic_json(number_float_t(val)) + template , + detail::enable_if_t::value and + not std::is_same::value and + not detail::is_basic_json_nested_type< + basic_json_t, U>::value and + detail::has_to_json::value, + int> = 0> + basic_json(T && val) noexcept(noexcept(JSONSerializer::to_json( + std::declval(), std::forward(val)))) { - assert_invariant(); + JSONSerializer::to_json(*this, std::forward(val)); } /*! @@ -2651,146 +2933,6 @@ class basic_json /// @} private: - ////////////////// - // value access // - ////////////////// - - /// get an object (explicit) - template::value and - std::is_convertible::value, int>::type = 0> - T get_impl(T* /*unused*/) const - { - if (is_object()) - { - return T(m_value.object->begin(), m_value.object->end()); - } - - JSON_THROW(std::domain_error("type must be object, but is " + type_name())); - } - - /// get an object (explicit) - object_t get_impl(object_t* /*unused*/) const - { - if (is_object()) - { - return *(m_value.object); - } - - JSON_THROW(std::domain_error("type must be object, but is " + type_name())); - } - - /// get an array (explicit) - template::value and - not std::is_same::value and - not std::is_arithmetic::value and - not std::is_convertible::value and - not has_mapped_type::value, int>::type = 0> - T get_impl(T* /*unused*/) const - { - if (is_array()) - { - T to_vector; - std::transform(m_value.array->begin(), m_value.array->end(), - std::inserter(to_vector, to_vector.end()), [](basic_json i) - { - return i.get(); - }); - return to_vector; - } - - JSON_THROW(std::domain_error("type must be array, but is " + type_name())); - } - - /// get an array (explicit) - template::value and - not std::is_same::value, int>::type = 0> - std::vector get_impl(std::vector* /*unused*/) const - { - if (is_array()) - { - std::vector to_vector; - to_vector.reserve(m_value.array->size()); - std::transform(m_value.array->begin(), m_value.array->end(), - std::inserter(to_vector, to_vector.end()), [](basic_json i) - { - return i.get(); - }); - return to_vector; - } - - JSON_THROW(std::domain_error("type must be array, but is " + type_name())); - } - - /// get an array (explicit) - template::value and - not has_mapped_type::value, int>::type = 0> - T get_impl(T* /*unused*/) const - { - if (is_array()) - { - return T(m_value.array->begin(), m_value.array->end()); - } - - JSON_THROW(std::domain_error("type must be array, but is " + type_name())); - } - - /// get an array (explicit) - array_t get_impl(array_t* /*unused*/) const - { - if (is_array()) - { - return *(m_value.array); - } - - JSON_THROW(std::domain_error("type must be array, but is " + type_name())); - } - - /// get a string (explicit) - template::value, int>::type = 0> - T get_impl(T* /*unused*/) const - { - if (is_string()) - { - return *m_value.string; - } - - JSON_THROW(std::domain_error("type must be string, but is " + type_name())); - } - - /// get a number (explicit) - template::value, int>::type = 0> - T get_impl(T* /*unused*/) const - { - switch (m_type) - { - case value_t::number_integer: - { - return static_cast(m_value.number_integer); - } - - case value_t::number_unsigned: - { - return static_cast(m_value.number_unsigned); - } - - case value_t::number_float: - { - return static_cast(m_value.number_float); - } - - default: - { - JSON_THROW(std::domain_error("type must be number, but is " + type_name())); - } - } - } - /// get a boolean (explicit) boolean_t get_impl(boolean_t* /*unused*/) const { @@ -2918,49 +3060,92 @@ class basic_json } public: + /*! + @brief get special-case overload - /// @name value access - /// Direct access to the stored value of a JSON value. - /// @{ + This overloads avoids a lot of template boilerplate, it can be seen as the identity method + + @tparam T type; T == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template < + typename T, + detail::enable_if_t::type, + basic_json_t>::value, + int> = 0 > + basic_json get() const + { + return *this; + } /*! - @brief get a value (explicit) + @brief get overload for CopyConstructible and DefaultConstructible types + construct a default U value, and call @ref json_serializer from_json method with it - Explicit type conversion between the JSON value and a compatible value. + This overloads is chosen if: + - U is not @ref basic_json + - @ref json_serializer has a from_json method of the form: void from_json(const @ref basic_json&, U&) + - @ref json_serializer does not have a from_json method of the form: U from_json(const @ref basic_json&); - @tparam ValueType non-pointer type compatible to the JSON value, for - instance `int` for JSON integer numbers, `bool` for JSON booleans, or - `std::vector` types for JSON arrays + @return a value of type U - @return copy of the JSON value, converted to type @a ValueType + @throw what json_serializer from_json method throws - @throw std::domain_error in case passed type @a ValueType is incompatible - to JSON; example: `"type must be object, but is null"` + @since version 2.1.0 + */ + template < + typename T, + typename U = detail::uncvref_t, + detail::enable_if_t < + not std::is_same::value and + detail::has_from_json::value and + not detail::has_non_default_from_json::value, + int > = 0 > + U get() const noexcept(noexcept(JSONSerializer::from_json( + std::declval(), std::declval()))) + { + // we cannot static_assert on T being non-const, because there is support + // for get(), which is why we still need the uncvref + static_assert(not std::is_reference::value, "get cannot be used with reference types, you might want to use get_ref"); + static_assert(std::is_default_constructible::value, + "Types must be DefaultConstructible when used with get"); + U ret; + JSONSerializer::from_json(*this, ret); + return ret; + } - @complexity Linear in the size of the JSON value. + /*! + @brief get overload for types than cannot be default constructed or copy constructed - @liveexample{The example below shows several conversions from JSON values - to other types. There a few things to note: (1) Floating-point numbers can - be converted to integers\, (2) A JSON array can be converted to a standard - `std::vector`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`.,get__ValueType_const} + If @ref json_serializer has both overloads of from_json, this one is chosen - @internal - The idea of using a casted null pointer to choose the correct - implementation is from . - @endinternal + This overloads is chosen if: + - U is not @ref basic_json + - @ref json_serializer has a from_json method of the form: U from_json(const @ref basic_json&); - @sa @ref operator ValueType() const for implicit conversion - @sa @ref get() for pointer-member access + @return a value of type U - @since version 1.0.0 + @throw what json_serializer from_json method throws + + @since version 2.1.0 */ - template::value, int>::type = 0> - ValueType get() const + template < + typename T, + typename U = detail::uncvref_t, + detail::enable_if_t::value and + detail::has_non_default_from_json::value, + int> = 0 > + U get() const noexcept(noexcept(JSONSerializer::from_json(std::declval()))) { - return get_impl(static_cast(nullptr)); + static_assert(not std::is_reference::value, "get cannot be used with reference types, you might want to use get_ref"); + return JSONSerializer::from_json(*this); } /*! @@ -5528,47 +5713,6 @@ class basic_json /// @} - - ////////////////////////////////////////// - // lexicographical comparison operators // - ////////////////////////////////////////// - - /// @name lexicographical comparison operators - /// @{ - - private: - /*! - @brief comparison operator for JSON types - - Returns an ordering that is similar to Python: - - order: null < boolean < number < object < array < string - - furthermore, each type is not smaller than itself - - @since version 1.0.0 - */ - friend bool operator<(const value_t lhs, const value_t rhs) noexcept - { - static constexpr std::array order = {{ - 0, // null - 3, // object - 4, // array - 5, // string - 1, // boolean - 2, // integer - 2, // unsigned - 2, // float - } - }; - - // discarded values are not comparable - if (lhs == value_t::discarded or rhs == value_t::discarded) - { - return false; - } - - return order[static_cast(lhs)] < order[static_cast(rhs)]; - } - public: /*! @brief comparison: equal @@ -7767,7 +7911,6 @@ class basic_json /// @} - private: /////////////////////////// // convenience functions // /////////////////////////// @@ -7782,29 +7925,36 @@ class basic_json @complexity Constant. + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,typename} + @since version 1.0.0 */ std::string type_name() const { - switch (m_type) { - case value_t::null: - return "null"; - case value_t::object: - return "object"; - case value_t::array: - return "array"; - case value_t::string: - return "string"; - case value_t::boolean: - return "boolean"; - case value_t::discarded: - return "discarded"; - default: - return "number"; + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } } + } + private: /*! @brief calculates the extra space to escape a JSON string @@ -8152,6 +8302,11 @@ class basic_json class primitive_iterator_t { public: + + difference_type get_value() const noexcept + { + return m_it; + } /// set iterator to a defined beginning void set_begin() noexcept { @@ -8176,16 +8331,87 @@ class basic_json return (m_it == end_value); } - /// return reference to the value to change and compare - operator difference_type& () noexcept + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { - return m_it; + return lhs.m_it == rhs.m_it; } - /// return value to compare - constexpr operator difference_type () const noexcept + friend constexpr bool operator!=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { - return m_it; + return !(lhs == rhs); + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + friend constexpr bool operator<=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it <= rhs.m_it; + } + + friend constexpr bool operator>(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it > rhs.m_it; + } + + friend constexpr bool operator>=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it >= rhs.m_it; + } + + primitive_iterator_t operator+(difference_type i) + { + auto result = *this; + result += i; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + friend std::ostream& operator<<(std::ostream& os, primitive_iterator_t it) + { + return os << it.m_it; + } + + primitive_iterator_t& operator++() + { + ++m_it; + return *this; + } + + primitive_iterator_t& operator++(int) + { + m_it++; + return *this; + } + + primitive_iterator_t& operator--() + { + --m_it; + return *this; + } + + primitive_iterator_t& operator--(int) + { + m_it--; + return *this; + } + + primitive_iterator_t& operator+=(difference_type n) + { + m_it += n; + return *this; + } + + primitive_iterator_t& operator-=(difference_type n) + { + m_it -= n; + return *this; } private: @@ -8890,7 +9116,7 @@ class basic_json default: { - if (m_it.primitive_iterator == -n) + if (m_it.primitive_iterator.get_value() == -n) { return *m_object; } @@ -10693,6 +10919,18 @@ class basic_json } private: + friend bool operator==(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return lhs.reference_tokens == rhs.reference_tokens; + } + + friend bool operator!=(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return !(lhs == rhs); + } + /// the reference tokens std::vector reference_tokens {}; }; @@ -11353,7 +11591,6 @@ class basic_json /// @} }; - ///////////// // presets // ///////////// diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 62213ad316..0ceb6bf618 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,12 +29,14 @@ add_executable(${JSON_UNITTEST_TARGET_NAME} "src/unit-meta.cpp" "src/unit-modifiers.cpp" "src/unit-msgpack.cpp" + "src/unit-noexcept.cpp" "src/unit-pointer_access.cpp" "src/unit-readme.cpp" "src/unit-reference_access.cpp" "src/unit-regression.cpp" "src/unit-serialization.cpp" "src/unit-testsuites.cpp" + "src/unit-udt.cpp" "src/unit-unicode.cpp" ) diff --git a/test/src/unit-class_const_iterator.cpp b/test/src/unit-class_const_iterator.cpp index 13ce7c3f36..dba96e1f31 100644 --- a/test/src/unit-class_const_iterator.cpp +++ b/test/src/unit-class_const_iterator.cpp @@ -91,7 +91,7 @@ TEST_CASE("const_iterator class") json j(json::value_t::null); json::const_iterator it(&j); it.set_begin(); - CHECK(it == j.cbegin()); + CHECK((it == j.cbegin())); } SECTION("object") @@ -99,7 +99,7 @@ TEST_CASE("const_iterator class") json j(json::value_t::object); json::const_iterator it(&j); it.set_begin(); - CHECK(it == j.cbegin()); + CHECK((it == j.cbegin())); } SECTION("array") @@ -107,7 +107,7 @@ TEST_CASE("const_iterator class") json j(json::value_t::array); json::const_iterator it(&j); it.set_begin(); - CHECK(it == j.cbegin()); + CHECK((it == j.cbegin())); } } @@ -118,7 +118,7 @@ TEST_CASE("const_iterator class") json j(json::value_t::null); json::const_iterator it(&j); it.set_end(); - CHECK(it == j.cend()); + CHECK((it == j.cend())); } SECTION("object") @@ -126,7 +126,7 @@ TEST_CASE("const_iterator class") json j(json::value_t::object); json::const_iterator it(&j); it.set_end(); - CHECK(it == j.cend()); + CHECK((it == j.cend())); } SECTION("array") @@ -134,7 +134,7 @@ TEST_CASE("const_iterator class") json j(json::value_t::array); json::const_iterator it(&j); it.set_end(); - CHECK(it == j.cend()); + CHECK((it == j.cend())); } } } @@ -220,48 +220,48 @@ TEST_CASE("const_iterator class") { json j(json::value_t::null); json::const_iterator it = j.cbegin(); - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); it++; - CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1)); + CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1)); } SECTION("number") { json j(17); json::const_iterator it = j.cbegin(); - CHECK(it.m_it.primitive_iterator == 0); + CHECK((it.m_it.primitive_iterator.m_it == 0)); it++; - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); it++; - CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1)); + CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1)); } SECTION("object") { json j({{"foo", "bar"}}); json::const_iterator it = j.cbegin(); - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin())); it++; - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end())); } SECTION("array") { json j({1, 2, 3, 4}); json::const_iterator it = j.cbegin(); - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin()); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin())); it++; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); it++; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); it++; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); it++; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end())); } } @@ -271,48 +271,48 @@ TEST_CASE("const_iterator class") { json j(json::value_t::null); json::const_iterator it = j.cbegin(); - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); ++it; - CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1)); + CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1)); } SECTION("number") { json j(17); json::const_iterator it = j.cbegin(); - CHECK(it.m_it.primitive_iterator == 0); + CHECK((it.m_it.primitive_iterator.m_it == 0)); ++it; - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); ++it; - CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1)); + CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1)); } SECTION("object") { json j({{"foo", "bar"}}); json::const_iterator it = j.cbegin(); - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin())); ++it; - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end())); } SECTION("array") { json j({1, 2, 3, 4}); json::const_iterator it = j.cbegin(); - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin()); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin())); ++it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); ++it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); ++it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); ++it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end())); } } @@ -322,46 +322,46 @@ TEST_CASE("const_iterator class") { json j(json::value_t::null); json::const_iterator it = j.cend(); - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); } SECTION("number") { json j(17); json::const_iterator it = j.cend(); - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); it--; - CHECK(it.m_it.primitive_iterator == 0); + CHECK((it.m_it.primitive_iterator.m_it == 0)); it--; - CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1)); + CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1)); } SECTION("object") { json j({{"foo", "bar"}}); json::const_iterator it = j.cend(); - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end())); it--; - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin())); } SECTION("array") { json j({1, 2, 3, 4}); json::const_iterator it = j.cend(); - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end())); it--; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); it--; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); it--; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); it--; - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); } } @@ -371,46 +371,46 @@ TEST_CASE("const_iterator class") { json j(json::value_t::null); json::const_iterator it = j.cend(); - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); } SECTION("number") { json j(17); json::const_iterator it = j.cend(); - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); --it; - CHECK(it.m_it.primitive_iterator == 0); + CHECK((it.m_it.primitive_iterator.m_it == 0)); --it; - CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1)); + CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1)); } SECTION("object") { json j({{"foo", "bar"}}); json::const_iterator it = j.cend(); - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end())); --it; - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin())); } SECTION("array") { json j({1, 2, 3, 4}); json::const_iterator it = j.cend(); - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end())); --it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); --it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); --it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); --it; - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); } } } diff --git a/test/src/unit-class_iterator.cpp b/test/src/unit-class_iterator.cpp index 640bc816ce..16833cd9d2 100644 --- a/test/src/unit-class_iterator.cpp +++ b/test/src/unit-class_iterator.cpp @@ -75,7 +75,7 @@ TEST_CASE("iterator class") json j(json::value_t::null); json::iterator it(&j); it.set_begin(); - CHECK(it == j.begin()); + CHECK((it == j.begin())); } SECTION("object") @@ -83,7 +83,7 @@ TEST_CASE("iterator class") json j(json::value_t::object); json::iterator it(&j); it.set_begin(); - CHECK(it == j.begin()); + CHECK((it == j.begin())); } SECTION("array") @@ -91,7 +91,7 @@ TEST_CASE("iterator class") json j(json::value_t::array); json::iterator it(&j); it.set_begin(); - CHECK(it == j.begin()); + CHECK((it == j.begin())); } } @@ -102,7 +102,7 @@ TEST_CASE("iterator class") json j(json::value_t::null); json::iterator it(&j); it.set_end(); - CHECK(it == j.end()); + CHECK((it == j.end())); } SECTION("object") @@ -110,7 +110,7 @@ TEST_CASE("iterator class") json j(json::value_t::object); json::iterator it(&j); it.set_end(); - CHECK(it == j.end()); + CHECK((it == j.end())); } SECTION("array") @@ -118,7 +118,7 @@ TEST_CASE("iterator class") json j(json::value_t::array); json::iterator it(&j); it.set_end(); - CHECK(it == j.end()); + CHECK((it == j.end())); } } } @@ -204,48 +204,48 @@ TEST_CASE("iterator class") { json j(json::value_t::null); json::iterator it = j.begin(); - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); it++; - CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1)); + CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1)); } SECTION("number") { json j(17); json::iterator it = j.begin(); - CHECK(it.m_it.primitive_iterator == 0); + CHECK((it.m_it.primitive_iterator.m_it == 0)); it++; - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); it++; - CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1)); + CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1)); } SECTION("object") { json j({{"foo", "bar"}}); json::iterator it = j.begin(); - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin())); it++; - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end())); } SECTION("array") { json j({1, 2, 3, 4}); json::iterator it = j.begin(); - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin()); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin())); it++; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); it++; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); it++; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); it++; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end())); } } @@ -255,48 +255,48 @@ TEST_CASE("iterator class") { json j(json::value_t::null); json::iterator it = j.begin(); - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); ++it; - CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1)); + CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1)); } SECTION("number") { json j(17); json::iterator it = j.begin(); - CHECK(it.m_it.primitive_iterator == 0); + CHECK((it.m_it.primitive_iterator.m_it == 0)); ++it; - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); ++it; - CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1)); + CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1)); } SECTION("object") { json j({{"foo", "bar"}}); json::iterator it = j.begin(); - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin())); ++it; - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end())); } SECTION("array") { json j({1, 2, 3, 4}); json::iterator it = j.begin(); - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin()); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin())); ++it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); ++it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); ++it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); ++it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end())); } } @@ -306,46 +306,46 @@ TEST_CASE("iterator class") { json j(json::value_t::null); json::iterator it = j.end(); - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); } SECTION("number") { json j(17); json::iterator it = j.end(); - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); it--; - CHECK(it.m_it.primitive_iterator == 0); + CHECK((it.m_it.primitive_iterator.m_it == 0)); it--; - CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1)); + CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1)); } SECTION("object") { json j({{"foo", "bar"}}); json::iterator it = j.end(); - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end())); it--; - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin())); } SECTION("array") { json j({1, 2, 3, 4}); json::iterator it = j.end(); - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end())); it--; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); it--; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); it--; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); it--; - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); } } @@ -355,46 +355,46 @@ TEST_CASE("iterator class") { json j(json::value_t::null); json::iterator it = j.end(); - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); } SECTION("number") { json j(17); json::iterator it = j.end(); - CHECK(it.m_it.primitive_iterator == 1); + CHECK((it.m_it.primitive_iterator.m_it == 1)); --it; - CHECK(it.m_it.primitive_iterator == 0); + CHECK((it.m_it.primitive_iterator.m_it == 0)); --it; - CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1)); + CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1)); } SECTION("object") { json j({{"foo", "bar"}}); json::iterator it = j.end(); - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end())); --it; - CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin()); + CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin())); } SECTION("array") { json j({1, 2, 3, 4}); json::iterator it = j.end(); - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end())); --it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); --it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); --it; - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); --it; - CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin()); - CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end()); + CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin())); + CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end())); } } } diff --git a/test/src/unit-class_lexer.cpp b/test/src/unit-class_lexer.cpp index 33ea610a51..71e75df43d 100644 --- a/test/src/unit-class_lexer.cpp +++ b/test/src/unit-class_lexer.cpp @@ -38,86 +38,86 @@ TEST_CASE("lexer class") { SECTION("structural characters") { - CHECK(json::lexer(reinterpret_cast("["), - 1).scan() == json::lexer::token_type::begin_array); - CHECK(json::lexer(reinterpret_cast("]"), - 1).scan() == json::lexer::token_type::end_array); - CHECK(json::lexer(reinterpret_cast("{"), - 1).scan() == json::lexer::token_type::begin_object); - CHECK(json::lexer(reinterpret_cast("}"), - 1).scan() == json::lexer::token_type::end_object); - CHECK(json::lexer(reinterpret_cast(","), - 1).scan() == json::lexer::token_type::value_separator); - CHECK(json::lexer(reinterpret_cast(":"), - 1).scan() == json::lexer::token_type::name_separator); + CHECK((json::lexer(reinterpret_cast("["), + 1).scan() == json::lexer::token_type::begin_array)); + CHECK((json::lexer(reinterpret_cast("]"), + 1).scan() == json::lexer::token_type::end_array)); + CHECK((json::lexer(reinterpret_cast("{"), + 1).scan() == json::lexer::token_type::begin_object)); + CHECK((json::lexer(reinterpret_cast("}"), + 1).scan() == json::lexer::token_type::end_object)); + CHECK((json::lexer(reinterpret_cast(","), + 1).scan() == json::lexer::token_type::value_separator)); + CHECK((json::lexer(reinterpret_cast(":"), + 1).scan() == json::lexer::token_type::name_separator)); } SECTION("literal names") { - CHECK(json::lexer(reinterpret_cast("null"), - 4).scan() == json::lexer::token_type::literal_null); - CHECK(json::lexer(reinterpret_cast("true"), - 4).scan() == json::lexer::token_type::literal_true); - CHECK(json::lexer(reinterpret_cast("false"), - 5).scan() == json::lexer::token_type::literal_false); + CHECK((json::lexer(reinterpret_cast("null"), + 4).scan() == json::lexer::token_type::literal_null)); + CHECK((json::lexer(reinterpret_cast("true"), + 4).scan() == json::lexer::token_type::literal_true)); + CHECK((json::lexer(reinterpret_cast("false"), + 5).scan() == json::lexer::token_type::literal_false)); } SECTION("numbers") { - CHECK(json::lexer(reinterpret_cast("0"), - 1).scan() == json::lexer::token_type::value_number); - CHECK(json::lexer(reinterpret_cast("1"), - 1).scan() == json::lexer::token_type::value_number); - CHECK(json::lexer(reinterpret_cast("2"), - 1).scan() == json::lexer::token_type::value_number); - CHECK(json::lexer(reinterpret_cast("3"), - 1).scan() == json::lexer::token_type::value_number); - CHECK(json::lexer(reinterpret_cast("4"), - 1).scan() == json::lexer::token_type::value_number); - CHECK(json::lexer(reinterpret_cast("5"), - 1).scan() == json::lexer::token_type::value_number); - CHECK(json::lexer(reinterpret_cast("6"), - 1).scan() == json::lexer::token_type::value_number); - CHECK(json::lexer(reinterpret_cast("7"), - 1).scan() == json::lexer::token_type::value_number); - CHECK(json::lexer(reinterpret_cast("8"), - 1).scan() == json::lexer::token_type::value_number); - CHECK(json::lexer(reinterpret_cast("9"), - 1).scan() == json::lexer::token_type::value_number); + CHECK((json::lexer(reinterpret_cast("0"), + 1).scan() == json::lexer::token_type::value_number)); + CHECK((json::lexer(reinterpret_cast("1"), + 1).scan() == json::lexer::token_type::value_number)); + CHECK((json::lexer(reinterpret_cast("2"), + 1).scan() == json::lexer::token_type::value_number)); + CHECK((json::lexer(reinterpret_cast("3"), + 1).scan() == json::lexer::token_type::value_number)); + CHECK((json::lexer(reinterpret_cast("4"), + 1).scan() == json::lexer::token_type::value_number)); + CHECK((json::lexer(reinterpret_cast("5"), + 1).scan() == json::lexer::token_type::value_number)); + CHECK((json::lexer(reinterpret_cast("6"), + 1).scan() == json::lexer::token_type::value_number)); + CHECK((json::lexer(reinterpret_cast("7"), + 1).scan() == json::lexer::token_type::value_number)); + CHECK((json::lexer(reinterpret_cast("8"), + 1).scan() == json::lexer::token_type::value_number)); + CHECK((json::lexer(reinterpret_cast("9"), + 1).scan() == json::lexer::token_type::value_number)); } SECTION("whitespace") { // result is end_of_input, because not token is following - CHECK(json::lexer(reinterpret_cast(" "), - 1).scan() == json::lexer::token_type::end_of_input); - CHECK(json::lexer(reinterpret_cast("\t"), - 1).scan() == json::lexer::token_type::end_of_input); - CHECK(json::lexer(reinterpret_cast("\n"), - 1).scan() == json::lexer::token_type::end_of_input); - CHECK(json::lexer(reinterpret_cast("\r"), - 1).scan() == json::lexer::token_type::end_of_input); - CHECK(json::lexer(reinterpret_cast(" \t\n\r\n\t "), - 7).scan() == json::lexer::token_type::end_of_input); + CHECK((json::lexer(reinterpret_cast(" "), + 1).scan() == json::lexer::token_type::end_of_input)); + CHECK((json::lexer(reinterpret_cast("\t"), + 1).scan() == json::lexer::token_type::end_of_input)); + CHECK((json::lexer(reinterpret_cast("\n"), + 1).scan() == json::lexer::token_type::end_of_input)); + CHECK((json::lexer(reinterpret_cast("\r"), + 1).scan() == json::lexer::token_type::end_of_input)); + CHECK((json::lexer(reinterpret_cast(" \t\n\r\n\t "), + 7).scan() == json::lexer::token_type::end_of_input)); } } SECTION("token_type_name") { - CHECK(json::lexer::token_type_name(json::lexer::token_type::uninitialized) == ""); - CHECK(json::lexer::token_type_name(json::lexer::token_type::literal_true) == "true literal"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::literal_false) == "false literal"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::literal_null) == "null literal"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::value_string) == "string literal"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::value_number) == "number literal"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::begin_array) == "'['"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::begin_object) == "'{'"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::end_array) == "']'"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::end_object) == "'}'"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::name_separator) == "':'"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::value_separator) == "','"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::parse_error) == ""); - CHECK(json::lexer::token_type_name(json::lexer::token_type::end_of_input) == "end of input"); + CHECK((json::lexer::token_type_name(json::lexer::token_type::uninitialized) == "")); + CHECK((json::lexer::token_type_name(json::lexer::token_type::literal_true) == "true literal")); + CHECK((json::lexer::token_type_name(json::lexer::token_type::literal_false) == "false literal")); + CHECK((json::lexer::token_type_name(json::lexer::token_type::literal_null) == "null literal")); + CHECK((json::lexer::token_type_name(json::lexer::token_type::value_string) == "string literal")); + CHECK((json::lexer::token_type_name(json::lexer::token_type::value_number) == "number literal")); + CHECK((json::lexer::token_type_name(json::lexer::token_type::begin_array) == "'['")); + CHECK((json::lexer::token_type_name(json::lexer::token_type::begin_object) == "'{'")); + CHECK((json::lexer::token_type_name(json::lexer::token_type::end_array) == "']'")); + CHECK((json::lexer::token_type_name(json::lexer::token_type::end_object) == "'}'")); + CHECK((json::lexer::token_type_name(json::lexer::token_type::name_separator) == "':'")); + CHECK((json::lexer::token_type_name(json::lexer::token_type::value_separator) == "','")); + CHECK((json::lexer::token_type_name(json::lexer::token_type::parse_error) == "")); + CHECK((json::lexer::token_type_name(json::lexer::token_type::end_of_input) == "end of input")); } SECTION("parse errors on first character") @@ -150,7 +150,7 @@ TEST_CASE("lexer class") case ('8'): case ('9'): { - CHECK(res != json::lexer::token_type::parse_error); + CHECK((res != json::lexer::token_type::parse_error)); break; } @@ -160,14 +160,14 @@ TEST_CASE("lexer class") case ('\n'): case ('\r'): { - CHECK(res == json::lexer::token_type::end_of_input); + CHECK((res == json::lexer::token_type::end_of_input)); break; } // anything else is not expected default: { - CHECK(res == json::lexer::token_type::parse_error); + CHECK((res == json::lexer::token_type::parse_error)); break; } } diff --git a/test/src/unit-conversions.cpp b/test/src/unit-conversions.cpp index b82127bbd0..994f882dd5 100644 --- a/test/src/unit-conversions.cpp +++ b/test/src/unit-conversions.cpp @@ -160,12 +160,30 @@ TEST_CASE("value conversion") { std::forward_list a = j.get>(); CHECK(json(a) == j); + + CHECK_THROWS_AS(json(json::value_t::null).get>(), std::logic_error); + CHECK_THROWS_WITH(json(json::value_t::null).get>(), + "type must be array, but is null"); } SECTION("std::vector") { std::vector a = j.get>(); CHECK(json(a) == j); + + CHECK_THROWS_AS(json(json::value_t::null).get>(), std::logic_error); + CHECK_THROWS_WITH(json(json::value_t::null).get>(), + "type must be array, but is null"); + +#if not defined(JSON_NOEXCEPTION) + SECTION("reserve is called on containers that supports it") + { + // making the call to from_json throw in order to check capacity + std::vector v; + CHECK_THROWS_AS(nlohmann::from_json(j, v), std::logic_error); + CHECK(v.capacity() == j.size()); + } +#endif } SECTION("std::deque") @@ -184,6 +202,8 @@ TEST_CASE("value conversion") 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::object).get>(), + "type must be array, but is object"); CHECK_THROWS_WITH(json(json::value_t::null).get(), "type must be array, but is null"); CHECK_THROWS_WITH(json(json::value_t::object).get(), @@ -1004,6 +1024,8 @@ TEST_CASE("value conversion") CHECK_THROWS_AS((json().get>()), std::logic_error); CHECK_THROWS_AS((json().get>()), std::logic_error); + // does type really must be an array? or it rather must not be null? + // that's what I thought when other test like this one broke CHECK_THROWS_WITH((json().get>()), "type must be array, but is null"); CHECK_THROWS_WITH((json().get>()), "type must be array, but is null"); CHECK_THROWS_WITH((json().get>()), "type must be array, but is null"); diff --git a/test/src/unit-noexcept.cpp b/test/src/unit-noexcept.cpp new file mode 100644 index 0000000000..b939db4e85 --- /dev/null +++ b/test/src/unit-noexcept.cpp @@ -0,0 +1,32 @@ +#include "catch.hpp" + +#include "json.hpp" + +using nlohmann::json; + +enum test +{ +}; + +struct pod {}; +struct pod_bis {}; + +void to_json(json&, pod) noexcept; +void to_json(json&, pod_bis); +void from_json(const json&, pod) noexcept; +void from_json(const json&, pod_bis); +static json j; + +static_assert(noexcept(json{}), ""); +static_assert(noexcept(nlohmann::to_json(j, 2)), ""); +static_assert(noexcept(nlohmann::to_json(j, 2.5)), ""); +static_assert(noexcept(nlohmann::to_json(j, true)), ""); +static_assert(noexcept(nlohmann::to_json(j, test{})), ""); +static_assert(noexcept(nlohmann::to_json(j, pod{})), ""); +static_assert(not noexcept(nlohmann::to_json(j, pod_bis{})), ""); +static_assert(noexcept(json(2)), ""); +static_assert(noexcept(json(test{})), ""); +static_assert(noexcept(json(pod{})), ""); +static_assert(noexcept(j.get()), ""); +static_assert(not noexcept(j.get()), ""); +static_assert(noexcept(json(pod{})), ""); diff --git a/test/src/unit-regression.cpp b/test/src/unit-regression.cpp index 7cb9169f25..6a5e0c64cf 100644 --- a/test/src/unit-regression.cpp +++ b/test/src/unit-regression.cpp @@ -63,10 +63,18 @@ TEST_CASE("regression tests") SECTION("pull request #71 - handle enum type") { - enum { t = 0 }; + enum { t = 0, u = 1}; json j = json::array(); j.push_back(t); + // maybe this is not the place to test this? + json j2 = u; + + auto anon_enum_value = j2.get(); + CHECK(u == anon_enum_value); + + static_assert(std::is_same::value, ""); + j.push_back(json::object( { {"game_type", t} diff --git a/test/src/unit-udt.cpp b/test/src/unit-udt.cpp new file mode 100644 index 0000000000..07f85b3782 --- /dev/null +++ b/test/src/unit-udt.cpp @@ -0,0 +1,680 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 2.0.7 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +Copyright (c) 2013-2016 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include +#include "catch.hpp" + +#include "json.hpp" + +using nlohmann::json; + +namespace udt +{ +enum class country +{ + china, + france, + russia +}; + +struct age +{ + int m_val; +}; + +struct name +{ + std::string m_val; +}; + +struct address +{ + std::string m_val; +}; + +struct person +{ + age m_age; + name m_name; + country m_country; +}; + +struct contact +{ + person m_person; + address m_address; +}; + +struct contact_book +{ + name m_book_name; + std::vector m_contacts; +}; +} + +// to_json methods +namespace udt +{ +// templates because of the custom_json tests (see below) +template +void to_json(BasicJsonType& j, age a) +{ + j = a.m_val; +} + +template +void to_json(BasicJsonType& j, const name& n) +{ + j = n.m_val; +} + +template +void to_json(BasicJsonType& j, country c) +{ + switch (c) + { + case country::china: + j = u8"中华人民共和国"; + return; + case country::france: + j = "France"; + return; + case country::russia: + j = u8"Российская Федерация"; + return; + } +} + +template +void to_json(BasicJsonType& j, const person& p) +{ + j = BasicJsonType{{"age", p.m_age}, {"name", p.m_name}, {"country", p.m_country}}; +} + +void to_json(nlohmann::json& j, const address& a) +{ + j = a.m_val; +} + +void to_json(nlohmann::json& j, const contact& c) +{ + j = json{{"person", c.m_person}, {"address", c.m_address}}; +} + +void to_json(nlohmann::json& j, const contact_book& cb) +{ + j = json{{"name", cb.m_book_name}, {"contacts", cb.m_contacts}}; +} + +// operators +bool operator==(age lhs, age rhs) +{ + return lhs.m_val == rhs.m_val; +} + +bool operator==(const address& lhs, const address& rhs) +{ + return lhs.m_val == rhs.m_val; +} + +bool operator==(const name& lhs, const name& rhs) +{ + return lhs.m_val == rhs.m_val; +} + +bool operator==(const person& lhs, const person& rhs) +{ + return std::tie(lhs.m_name, lhs.m_age) == std::tie(rhs.m_name, rhs.m_age); +} + +bool operator==(const contact& lhs, const contact& rhs) +{ + return std::tie(lhs.m_person, lhs.m_address) == + std::tie(rhs.m_person, rhs.m_address); +} + +bool operator==(const contact_book& lhs, const contact_book& rhs) +{ + return std::tie(lhs.m_book_name, lhs.m_contacts) == + std::tie(rhs.m_book_name, rhs.m_contacts); +} +} + +// from_json methods +namespace udt +{ +template +void from_json(const BasicJsonType& j, age& a) +{ + a.m_val = j.template get(); +} + +template +void from_json(const BasicJsonType& j, name& n) +{ + n.m_val = j.template get(); +} + +template +void from_json(const BasicJsonType& j, country& c) +{ + const auto str = j.template get(); + static const std::map m = + { + {u8"中华人民共和国", country::china}, + {"France", country::france}, + {"Российская Федерация", country::russia} + }; + + const auto it = m.find(str); + // TODO test exceptions + c = it->second; +} + +template +void from_json(const BasicJsonType& j, person& p) +{ + p.m_age = j["age"].template get(); + p.m_name = j["name"].template get(); + p.m_country = j["country"].template get(); +} + +void from_json(const nlohmann::json& j, address& a) +{ + a.m_val = j.get(); +} + +void from_json(const nlohmann::json& j, contact& c) +{ + c.m_person = j["person"].get(); + c.m_address = j["address"].get
(); +} + +void from_json(const nlohmann::json& j, contact_book& cb) +{ + cb.m_book_name = j["name"].get(); + cb.m_contacts = j["contacts"].get>(); +} +} + +TEST_CASE("basic usage", "[udt]") +{ + + // a bit narcissic maybe :) ? + const udt::age a + { + 23 + }; + const udt::name n{"theo"}; + const udt::country c{udt::country::france}; + const udt::person sfinae_addict{a, n, c}; + const udt::person senior_programmer{{42}, {u8"王芳"}, udt::country::china}; + const udt::address addr{"Paris"}; + const udt::contact cpp_programmer{sfinae_addict, addr}; + const udt::contact_book book{{"C++"}, {cpp_programmer, {senior_programmer, addr}}}; + + SECTION("conversion to json via free-functions") + { + CHECK(json(a) == json(23)); + CHECK(json(n) == json("theo")); + CHECK(json(c) == json("France")); + CHECK(json(sfinae_addict) == R"({"name":"theo", "age":23, "country":"France"})"_json); + CHECK(json("Paris") == json(addr)); + CHECK(json(cpp_programmer) == + R"({"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"})"_json); + + CHECK( + json(book) == + u8R"({"name":"C++", "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json); + + } + + SECTION("conversion from json via free-functions") + { + const auto big_json = + u8R"({"name":"C++", "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json; + SECTION("via explicit calls to get") + { + const auto parsed_book = big_json.get(); + const auto book_name = big_json["name"].get(); + const auto contacts = + big_json["contacts"].get>(); + const auto contact_json = big_json["contacts"].at(0); + const auto contact = contact_json.get(); + const auto person = contact_json["person"].get(); + const auto address = contact_json["address"].get(); + const auto age = contact_json["person"]["age"].get(); + const auto country = + contact_json["person"]["country"].get(); + const auto name = contact_json["person"]["name"].get(); + + CHECK(age == a); + CHECK(name == n); + CHECK(country == c); + CHECK(address == addr); + CHECK(person == sfinae_addict); + CHECK(contact == cpp_programmer); + CHECK(contacts == book.m_contacts); + CHECK(book_name == udt::name{"C++"}); + CHECK(book == parsed_book); + } + + SECTION("implicit conversions") + { + const udt::contact_book parsed_book = big_json; + const udt::name book_name = big_json["name"]; + const std::vector contacts = big_json["contacts"]; + const auto contact_json = big_json["contacts"].at(0); + const udt::contact contact = contact_json; + const udt::person person = contact_json["person"]; + const udt::address address = contact_json["address"]; + const udt::age age = contact_json["person"]["age"]; + const udt::country country = contact_json["person"]["country"]; + const udt::name name = contact_json["person"]["name"]; + + CHECK(age == a); + CHECK(name == n); + CHECK(country == c); + CHECK(address == addr); + CHECK(person == sfinae_addict); + CHECK(contact == cpp_programmer); + CHECK(contacts == book.m_contacts); + CHECK(book_name == udt::name{"C++"}); + CHECK(book == parsed_book); + } + } +} + +namespace udt +{ +struct legacy_type +{ + std::string number; +}; +} + +namespace nlohmann +{ +template +struct adl_serializer> +{ + static void to_json(json& j, const std::shared_ptr& opt) + { + if (opt) + { + j = *opt; + } + else + { + j = nullptr; + } + } + + static void from_json(const json& j, std::shared_ptr& opt) + { + if (j.is_null()) + { + opt = nullptr; + } + else + { + opt.reset(new T(j.get())); + } + } +}; + +template <> +struct adl_serializer +{ + static void to_json(json& j, const udt::legacy_type& l) + { + j = std::stoi(l.number); + } + + static void from_json(const json& j, udt::legacy_type& l) + { + l.number = std::to_string(j.get()); + } +}; +} + +TEST_CASE("adl_serializer specialization", "[udt]") +{ + SECTION("partial specialization") + { + SECTION("to_json") + { + std::shared_ptr optPerson; + + json j = optPerson; + CHECK(j.is_null()); + + optPerson.reset(new udt::person{{42}, {"John Doe"}, udt::country::russia}); + j = optPerson; + CHECK_FALSE(j.is_null()); + + CHECK(j.get() == *optPerson); + } + + SECTION("from_json") + { + auto person = udt::person{{42}, {"John Doe"}, udt::country::russia}; + json j = person; + + auto optPerson = j.get>(); + REQUIRE(optPerson); + CHECK(*optPerson == person); + + j = nullptr; + optPerson = j.get>(); + CHECK(!optPerson); + } + } + + SECTION("total specialization") + { + SECTION("to_json") + { + udt::legacy_type lt{"4242"}; + + json j = lt; + CHECK(j.get() == 4242); + } + + SECTION("from_json") + { + json j = 4242; + auto lt = j.get(); + CHECK(lt.number == "4242"); + } + } +} + +namespace nlohmann +{ +template <> +struct adl_serializer> +{ + using type = std::vector; + static void to_json(json& j, const type&) + { + j = "hijacked!"; + } + + static void from_json(const json&, type& opt) + { + opt = {42.0, 42.0, 42.0}; + } + + // preferred version + static type from_json(const json&) + { + return {4.0, 5.0, 6.0}; + } +}; +} + +TEST_CASE("even supported types can be specialized", "[udt]") +{ + json j = std::vector {1.0, 2.0, 3.0}; + CHECK(j.dump() == R"("hijacked!")"); + auto f = j.get>(); + // the single argument from_json method is preferred + CHECK((f == std::vector {4.0, 5.0, 6.0})); +} + +namespace nlohmann +{ +template +struct adl_serializer> +{ + static void to_json(json& j, const std::unique_ptr& opt) + { + if (opt) + { + j = *opt; + } + else + { + j = nullptr; + } + } + + // this is the overload needed for non-copyable types, + static std::unique_ptr from_json(const json& j) + { + if (j.is_null()) + { + return nullptr; + } + else + { + return std::unique_ptr(new T(j.get())); + } + } +}; +} + +TEST_CASE("Non-copyable types", "[udt]") +{ + SECTION("to_json") + { + std::unique_ptr optPerson; + + json j = optPerson; + CHECK(j.is_null()); + + optPerson.reset(new udt::person{{42}, {"John Doe"}, udt::country::russia}); + j = optPerson; + CHECK_FALSE(j.is_null()); + + CHECK(j.get() == *optPerson); + } + + SECTION("from_json") + { + auto person = udt::person{{42}, {"John Doe"}, udt::country::russia}; + json j = person; + + auto optPerson = j.get>(); + REQUIRE(optPerson); + CHECK(*optPerson == person); + + j = nullptr; + optPerson = j.get>(); + CHECK(!optPerson); + } +} + +// custom serializer - advanced usage +// pack structs that are pod-types (but not scalar types) +// relies on adl for any other type +template +struct pod_serializer +{ + // use adl for non-pods, or scalar types + template < + typename BasicJsonType, typename U = T, + typename std::enable_if < + not(std::is_pod::value and std::is_class::value), int >::type = 0 > + static void from_json(const BasicJsonType& j, U& t) + { + using nlohmann::from_json; + from_json(j, t); + } + + // special behaviour for pods + template ::value and std::is_class::value, int>::type = 0> + static void from_json(const BasicJsonType& j, U& t) + { + std::uint64_t value; + // TODO The following block is no longer relevant in this serializer, make another one that shows the issue + // the problem arises only when one from_json method is defined without any constraint + // + // Why cannot we simply use: j.get() ? + // Well, with the current experiment, the get method looks for a from_json + // function, which we are currently defining! + // This would end up in a stack overflow. Calling nlohmann::from_json is a + // workaround (is it?). + // I shall find a good way to avoid this once all constructors are converted + // to free methods + // + // In short, constructing a json by constructor calls to_json + // calling get calls from_json, for now, we cannot do this in custom + // serializers + nlohmann::from_json(j, value); + auto bytes = static_cast(static_cast(&value)); + std::memcpy(&t, bytes, sizeof(value)); + } + + template < + typename BasicJsonType, typename U = T, + typename std::enable_if < + not(std::is_pod::value and std::is_class::value), int >::type = 0 > + static void to_json(BasicJsonType& j, const T& t) + { + using nlohmann::to_json; + to_json(j, t); + } + + template ::value and std::is_class::value, int>::type = 0> + static void to_json(BasicJsonType& j, const T& t) noexcept + { + auto bytes = static_cast< const unsigned char*>(static_cast(&t)); + std::uint64_t value = bytes[0]; + for (auto i = 1; i < 8; ++i) + value |= std::uint64_t{bytes[i]} << 8 * i; + nlohmann::to_json(j, value); + } +}; + +namespace udt +{ +struct small_pod +{ + int begin; + char middle; + short end; +}; + +struct non_pod +{ + std::string s; +}; + +template +void to_json(BasicJsonType& j, const non_pod& np) +{ + j = np.s; +} + +template +void from_json(const BasicJsonType& j, non_pod& np) +{ + np.s = j.template get(); +} + +bool operator==(small_pod lhs, small_pod rhs) noexcept +{ + return std::tie(lhs.begin, lhs.middle, lhs.end) == + std::tie(rhs.begin, rhs.middle, rhs.end); +} + +bool operator==(const non_pod& lhs, const non_pod& rhs) noexcept +{ + return lhs.s == rhs.s; +} + +std::ostream& operator<<(std::ostream& os, small_pod l) +{ + return os << "begin: " << l.begin << ", middle: " << l.middle << ", end: " << l.end; +} +} + +TEST_CASE("custom serializer for pods", "[udt]") +{ + using custom_json = + nlohmann::basic_json; + + auto p = udt::small_pod{42, '/', 42}; + custom_json j = p; + + auto p2 = j.get(); + + CHECK(p == p2); + + auto np = udt::non_pod{{"non-pod"}}; + custom_json j2 = np; + auto np2 = j2.get(); + CHECK(np == np2); +} + +template +struct another_adl_serializer; + +using custom_json = nlohmann::basic_json; + +template +struct another_adl_serializer +{ + static void from_json(const custom_json& j, T& t) + { + using nlohmann::from_json; + from_json(j, t); + } + + static void to_json(custom_json& j, const T& t) + { + using nlohmann::to_json; + to_json(j, t); + } +}; + +TEST_CASE("custom serializer that does adl by default", "[udt]") +{ + using json = nlohmann::json; + + auto me = udt::person{23, "theo", udt::country::france}; + + json j = me; + custom_json cj = me; + + CHECK(j.dump() == cj.dump()); + + CHECK(me == j.get()); + CHECK(me == cj.get()); +}