diff --git a/README.md b/README.md index 69676370..e2e36fd1 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/from arbitrary types](#arbitrary-types-conversions) - [Binary formats (CBOR and MessagePack)](#binary-formats-cbor-and-messagepack) - [Supported compilers](#supported-compilers) - [License](#license) @@ -442,6 +443,224 @@ 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 { + // a simple struct to model a person + struct person { + std::string name; + std::string address; + int age; + }; +} + +ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60}; + +// convert to JSON: copy each value into the JSON object +json j; +j["name"] = p.name; +j["address"] = p.address; +j["age"] = p.age; + +// ... + +// convert from JSON: copy each value from the JSON object +ns::person p { + j["name"].get(), + j["address"].get(), + j["age"].get() +}; +``` + +It works, but that's quite a lot of boilerplate... Fortunately, there's a better way: + +```cpp +// create a person +ns::person p {"Ned Flanders", "744 Evergreen Terrace", 60}; + +// conversion: person -> json +json j = p; + +std::cout << j << std::endl; +// {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"} + +// conversion: json -> person +ns::person p2 = j; + +// that's it +assert(p == p2); +``` + +#### Basic usage + +To make this work with one of your types, you only need to provide two functions: + +```cpp +using nlohmann::json; + +namespace ns { + void to_json(json& j, const person& p) { + j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}}; + } + + void from_json(const json& 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 (which can be the global 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](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). (There is a way to bypass this requirement 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](http://en.cppreference.com/w/cpp/concept/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`](https://github.com/nlohmann/json/blob/develop/test/src/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 { + template + static void to_json(BasicJsonType& 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 BasicJsonType& 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(BasicJsonType& j, const T& value) { + // this calls BasicJsonType::json_serializer::to_json(j, value); + // if BasicJsonType::json_serializer == bad_serializer ... oops! + j = value; + } + + template + static void to_json(const BasicJsonType& j, T& value) { + // this calls BasicJsonType::json_serializer::from_json(j, value); + // if BasicJsonType::json_serializer == bad_serializer ... oops! + value = j.template get(); // oops! + } +}; +``` + ### Binary formats (CBOR and MessagePack) Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports [CBOR](http://cbor.io) (Concise Binary Object Representation) and [MessagePack](http://msgpack.org) to efficiently encode JSON values to byte vectors and to decode such vectors. @@ -546,7 +765,7 @@ I deeply appreciate the help of the following people. - [Eric Cornelius](https://github.com/EricMCornelius) pointed out a bug in the handling with NaN and infinity values. He also improved the performance of the string escaping. - [易思龙](https://github.com/likebeta) implemented a conversion from anonymous enums. - [kepkin](https://github.com/kepkin) patiently pushed forward the support for Microsoft Visual studio. -- [gregmarr](https://github.com/gregmarr) simplified the implementation of reverse iterators and helped with numerous hints and improvements. +- [gregmarr](https://github.com/gregmarr) simplified the implementation of reverse iterators and helped with numerous hints and improvements. In particular, he pushed forward the implementation of user-defined types. - [Caio Luppi](https://github.com/caiovlp) fixed a bug in the Unicode handling. - [dariomt](https://github.com/dariomt) fixed some typos in the examples. - [Daniel Frey](https://github.com/d-frey) cleaned up some pointers and implemented exception-safe memory allocation. @@ -574,7 +793,7 @@ I deeply appreciate the help of the following people. - [duncanwerner](https://github.com/duncanwerner) found a really embarrassing performance regression in the 2.0.0 release. - [Damien](https://github.com/dtoma) fixed one of the last conversion warnings. - [Thomas Braun](https://github.com/t-b) fixed a warning in a test case. -- [Théo DELRIEU](https://github.com/theodelrieu) patiently and constructively oversaw the long way toward [iterator-range parsing](https://github.com/nlohmann/json/issues/290). +- [Théo DELRIEU](https://github.com/theodelrieu) patiently and constructively oversaw the long way toward [iterator-range parsing](https://github.com/nlohmann/json/issues/290). He also implemented the magic behind the serialization/deserialization of user-defined types. - [Stefan](https://github.com/5tefan) fixed a minor issue in the documentation. - [Vasil Dimov](https://github.com/vasild) fixed the documentation regarding conversions from `std::multiset`. - [ChristophJud](https://github.com/ChristophJud) overworked the CMake files to ease project inclusion. @@ -588,6 +807,9 @@ I deeply appreciate the help of the following people. - [Bosswestfalen](https://github.com/Bosswestfalen) merged two iterator classes into a smaller one. - [Daniel599](https://github.com/Daniel599) helped to get Travis execute the tests with Clang's sanitizers. - [Jonathan Lee](https://github.com/vjon) fixed an example in the README file. +- [gnzlbg](https://github.com/gnzlbg) supported the implementation of user-defined types. +- [Alexej Harm](https://github.com/qis) helped to get the user-defined types working with Visual Studio. +- [Jared Grubb](https://github.com/jaredgrubb) supported the implementation of user-defined types. Thanks a lot for helping out! Please [let me know](mailto:mail@nlohmann.me) if I forgot someone. @@ -611,10 +833,11 @@ Thanks a lot for helping out! Please [let me know](mailto:mail@nlohmann.me) if I To compile and run the tests, you need to execute ```sh -$ make check +$ make json_unit -Ctest +$ ./test/json_unit "*"" =============================================================================== -All tests passed (11202040 assertions in 44 test cases) +All tests passed (11202052 assertions in 47 test cases) ``` Alternatively, you can use [CMake](https://cmake.org) and run diff --git a/doc/Doxyfile b/doc/Doxyfile index 5064a0a0..4d511af3 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/basic_json__CompatibleArrayType.cpp b/doc/examples/basic_json__CompatibleArrayType.cpp deleted file mode 100644 index 26a1a101..00000000 --- a/doc/examples/basic_json__CompatibleArrayType.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include -#include -#include -#include -#include -#include - -using json = nlohmann::json; - -int main() -{ - // create an array from std::vector - std::vector c_vector {1, 2, 3, 4}; - json j_vec(c_vector); - - // create an array from std::deque - std::deque c_deque {1.2, 2.3, 3.4, 5.6}; - json j_deque(c_deque); - - // create an array from std::list - std::list c_list {true, true, false, true}; - json j_list(c_list); - - // create an array from std::forward_list - std::forward_list c_flist {12345678909876, 23456789098765, 34567890987654, 45678909876543}; - json j_flist(c_flist); - - // create an array from std::array - std::array c_array {{1, 2, 3, 4}}; - json j_array(c_array); - - // create an array from std::set - std::set c_set {"one", "two", "three", "four", "one"}; - json j_set(c_set); // only one entry for "one" is used - - // create an array from std::unordered_set - std::unordered_set c_uset {"one", "two", "three", "four", "one"}; - json j_uset(c_uset); // only one entry for "one" is used - - // create an array from std::multiset - std::multiset c_mset {"one", "two", "one", "four"}; - json j_mset(c_mset); // both entries for "one" are used - - // create an array from std::unordered_multiset - std::unordered_multiset c_umset {"one", "two", "one", "four"}; - json j_umset(c_umset); // both entries for "one" are used - - // serialize the JSON arrays - std::cout << j_vec << '\n'; - std::cout << j_deque << '\n'; - std::cout << j_list << '\n'; - std::cout << j_flist << '\n'; - std::cout << j_array << '\n'; - std::cout << j_set << '\n'; - std::cout << j_uset << '\n'; - std::cout << j_mset << '\n'; - std::cout << j_umset << '\n'; -} diff --git a/doc/examples/basic_json__CompatibleArrayType.link b/doc/examples/basic_json__CompatibleArrayType.link deleted file mode 100644 index 95ecb050..00000000 --- a/doc/examples/basic_json__CompatibleArrayType.link +++ /dev/null @@ -1 +0,0 @@ -online \ No newline at end of file diff --git a/doc/examples/basic_json__CompatibleArrayType.output b/doc/examples/basic_json__CompatibleArrayType.output deleted file mode 100644 index 428505a1..00000000 --- a/doc/examples/basic_json__CompatibleArrayType.output +++ /dev/null @@ -1,9 +0,0 @@ -[1,2,3,4] -[1.2,2.3,3.4,5.6] -[true,true,false,true] -[12345678909876,23456789098765,34567890987654,45678909876543] -[1,2,3,4] -["four","one","three","two"] -["four","three","two","one"] -["four","one","one","two"] -["four","two","one","one"] diff --git a/doc/examples/basic_json__CompatibleIntegerNumberType.cpp b/doc/examples/basic_json__CompatibleIntegerNumberType.cpp deleted file mode 100644 index 50e751d1..00000000 --- a/doc/examples/basic_json__CompatibleIntegerNumberType.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -using json = nlohmann::json; - -int main() -{ - // create values of different integer types - short n42 = 42; - int n23 = 23; - long n1024 = 1024; - int_least32_t n17 = 17; - uint8_t n8 = 8; - - // create JSON numbers - json j42(n42); - json j23(n23); - json j1024(n1024); - json j17(n17); - json j8(n8); - - // serialize the JSON numbers - std::cout << j42 << '\n'; - std::cout << j23 << '\n'; - std::cout << j1024 << '\n'; - std::cout << j17 << '\n'; - std::cout << j8 << '\n'; -} diff --git a/doc/examples/basic_json__CompatibleIntegerNumberType.link b/doc/examples/basic_json__CompatibleIntegerNumberType.link deleted file mode 100644 index 7a37e9eb..00000000 --- a/doc/examples/basic_json__CompatibleIntegerNumberType.link +++ /dev/null @@ -1 +0,0 @@ -online \ No newline at end of file diff --git a/doc/examples/basic_json__CompatibleIntegerNumberType.output b/doc/examples/basic_json__CompatibleIntegerNumberType.output deleted file mode 100644 index c7f24d63..00000000 --- a/doc/examples/basic_json__CompatibleIntegerNumberType.output +++ /dev/null @@ -1,5 +0,0 @@ -42 -23 -1024 -17 -8 diff --git a/doc/examples/basic_json__CompatibleNumberFloatType.cpp b/doc/examples/basic_json__CompatibleNumberFloatType.cpp deleted file mode 100644 index 6f8d3f67..00000000 --- a/doc/examples/basic_json__CompatibleNumberFloatType.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include - -using json = nlohmann::json; - -int main() -{ - // create values of different floating-point types - float f42 = 42.23; - float f_nan = 1.0f / 0.0f; - double f23 = 23.42; - - // create JSON numbers - json j42(f42); - json j_nan(f_nan); - json j23(f23); - - // serialize the JSON numbers - std::cout << j42 << '\n'; - std::cout << j_nan << '\n'; - std::cout << j23 << '\n'; -} diff --git a/doc/examples/basic_json__CompatibleNumberFloatType.link b/doc/examples/basic_json__CompatibleNumberFloatType.link deleted file mode 100644 index 9fbc7317..00000000 --- a/doc/examples/basic_json__CompatibleNumberFloatType.link +++ /dev/null @@ -1 +0,0 @@ -online \ No newline at end of file diff --git a/doc/examples/basic_json__CompatibleNumberFloatType.output b/doc/examples/basic_json__CompatibleNumberFloatType.output deleted file mode 100644 index 64bb796c..00000000 --- a/doc/examples/basic_json__CompatibleNumberFloatType.output +++ /dev/null @@ -1,3 +0,0 @@ -42.2299995422363 -null -23.42 diff --git a/doc/examples/basic_json__CompatibleObjectType.cpp b/doc/examples/basic_json__CompatibleObjectType.cpp deleted file mode 100644 index d284b697..00000000 --- a/doc/examples/basic_json__CompatibleObjectType.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include - -using json = nlohmann::json; - -int main() -{ - // create an object from std::map - std::map c_map - { - {"one", 1}, {"two", 2}, {"three", 3} - }; - json j_map(c_map); - - // create an object from std::unordered_map - std::unordered_map c_umap - { - {"one", 1.2}, {"two", 2.3}, {"three", 3.4} - }; - json j_umap(c_umap); - - // create an object from std::multimap - std::multimap c_mmap - { - {"one", true}, {"two", true}, {"three", false}, {"three", true} - }; - json j_mmap(c_mmap); // only one entry for key "three" is used - - // create an object from std::unordered_multimap - std::unordered_multimap c_ummap - { - {"one", true}, {"two", true}, {"three", false}, {"three", true} - }; - json j_ummap(c_ummap); // only one entry for key "three" is used - - // serialize the JSON objects - std::cout << j_map << '\n'; - std::cout << j_umap << '\n'; - std::cout << j_mmap << '\n'; - std::cout << j_ummap << '\n'; -} diff --git a/doc/examples/basic_json__CompatibleObjectType.link b/doc/examples/basic_json__CompatibleObjectType.link deleted file mode 100644 index 7512fb35..00000000 --- a/doc/examples/basic_json__CompatibleObjectType.link +++ /dev/null @@ -1 +0,0 @@ -online \ No newline at end of file diff --git a/doc/examples/basic_json__CompatibleObjectType.output b/doc/examples/basic_json__CompatibleObjectType.output deleted file mode 100644 index c70f7184..00000000 --- a/doc/examples/basic_json__CompatibleObjectType.output +++ /dev/null @@ -1,4 +0,0 @@ -{"one":1,"three":3,"two":2} -{"one":1.2,"three":3.4,"two":2.3} -{"one":true,"three":false,"two":true} -{"one":true,"three":false,"two":true} diff --git a/doc/examples/basic_json__CompatibleStringType.cpp b/doc/examples/basic_json__CompatibleStringType.cpp deleted file mode 100644 index a0f3b4f6..00000000 --- a/doc/examples/basic_json__CompatibleStringType.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include - -using json = nlohmann::json; - -int main() -{ - // create a string value - std::string s = "The quick brown fox jumps over the lazy dog."; - - // create a JSON string value - json j = s; - - // serialize the JSON string - std::cout << j << '\n'; -} diff --git a/doc/examples/basic_json__CompatibleStringType.link b/doc/examples/basic_json__CompatibleStringType.link deleted file mode 100644 index 351d6c0c..00000000 --- a/doc/examples/basic_json__CompatibleStringType.link +++ /dev/null @@ -1 +0,0 @@ -online \ No newline at end of file diff --git a/doc/examples/basic_json__CompatibleStringType.output b/doc/examples/basic_json__CompatibleStringType.output deleted file mode 100644 index 1316dd98..00000000 --- a/doc/examples/basic_json__CompatibleStringType.output +++ /dev/null @@ -1 +0,0 @@ -"The quick brown fox jumps over the lazy dog." diff --git a/doc/examples/basic_json__CompatibleType.cpp b/doc/examples/basic_json__CompatibleType.cpp new file mode 100644 index 00000000..ff564a72 --- /dev/null +++ b/doc/examples/basic_json__CompatibleType.cpp @@ -0,0 +1,211 @@ +#include +#include +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +int main() +{ + // ============ + // object types + // ============ + + // create an object from an object_t value + json::object_t object_value = { {"one", 1}, {"two", 2} }; + json j_object_t(object_value); + + // create an object from std::map + std::map c_map + { + {"one", 1}, {"two", 2}, {"three", 3} + }; + json j_map(c_map); + + // create an object from std::unordered_map + std::unordered_map c_umap + { + {"one", 1.2}, {"two", 2.3}, {"three", 3.4} + }; + json j_umap(c_umap); + + // create an object from std::multimap + std::multimap c_mmap + { + {"one", true}, {"two", true}, {"three", false}, {"three", true} + }; + json j_mmap(c_mmap); // only one entry for key "three" is used + + // create an object from std::unordered_multimap + std::unordered_multimap c_ummap + { + {"one", true}, {"two", true}, {"three", false}, {"three", true} + }; + json j_ummap(c_ummap); // only one entry for key "three" is used + + // serialize the JSON objects + std::cout << j_object_t << '\n'; + std::cout << j_map << '\n'; + std::cout << j_umap << '\n'; + std::cout << j_mmap << '\n'; + std::cout << j_ummap << "\n\n"; + + + // =========== + // array types + // =========== + + // create an array from an array_t value + json::array_t array_value = {"one", "two", 3, 4.5, false}; + json j_array_t(array_value); + + // create an array from std::vector + std::vector c_vector {1, 2, 3, 4}; + json j_vec(c_vector); + + // create an array from std::deque + std::deque c_deque {1.2, 2.3, 3.4, 5.6}; + json j_deque(c_deque); + + // create an array from std::list + std::list c_list {true, true, false, true}; + json j_list(c_list); + + // create an array from std::forward_list + std::forward_list c_flist {12345678909876, 23456789098765, 34567890987654, 45678909876543}; + json j_flist(c_flist); + + // create an array from std::array + std::array c_array {{1, 2, 3, 4}}; + json j_array(c_array); + + // create an array from std::set + std::set c_set {"one", "two", "three", "four", "one"}; + json j_set(c_set); // only one entry for "one" is used + + // create an array from std::unordered_set + std::unordered_set c_uset {"one", "two", "three", "four", "one"}; + json j_uset(c_uset); // only one entry for "one" is used + + // create an array from std::multiset + std::multiset c_mset {"one", "two", "one", "four"}; + json j_mset(c_mset); // both entries for "one" are used + + // create an array from std::unordered_multiset + std::unordered_multiset c_umset {"one", "two", "one", "four"}; + json j_umset(c_umset); // both entries for "one" are used + + // serialize the JSON arrays + std::cout << j_array_t << '\n'; + std::cout << j_vec << '\n'; + std::cout << j_deque << '\n'; + std::cout << j_list << '\n'; + std::cout << j_flist << '\n'; + std::cout << j_array << '\n'; + std::cout << j_set << '\n'; + std::cout << j_uset << '\n'; + std::cout << j_mset << '\n'; + std::cout << j_umset << "\n\n"; + + + // ============ + // string types + // ============ + + // create string from a string_t value + json::string_t string_value = "The quick brown fox jumps over the lazy dog."; + json j_string_t(string_value); + + // create a JSON string directly from a string literal + json j_string_literal("The quick brown fox jumps over the lazy dog."); + + // create string from std::string + std::string s_stdstring = "The quick brown fox jumps over the lazy dog."; + json j_stdstring(s_stdstring); + + // serialize the JSON strings + std::cout << j_string_t << '\n'; + std::cout << j_string_literal << '\n'; + std::cout << j_stdstring << "\n\n"; + + + // ============ + // number types + // ============ + + // create a JSON number from number_integer_t + json::number_integer_t value_integer_t = -42; + json j_integer_t(value_integer_t); + + // create a JSON number from number_unsigned_t + json::number_integer_t value_unsigned_t = 17; + json j_unsigned_t(value_unsigned_t); + + // create a JSON number from an anonymous enum + enum { enum_value = 17 }; + json j_enum(enum_value); + + // create values of different integer types + short n_short = 42; + int n_int = -23; + long n_long = 1024; + int_least32_t n_int_least32_t = -17; + uint8_t n_uint8_t = 8; + + // create (integer) JSON numbers + json j_short(n_short); + json j_int(n_int); + json j_long(n_long); + json j_int_least32_t(n_int_least32_t); + json j_uint8_t(n_uint8_t); + + // create values of different floating-point types + json::number_float_t v_ok = 3.141592653589793; + json::number_float_t v_nan = NAN; + json::number_float_t v_infinity = INFINITY; + + // create values of different floating-point types + float n_float = 42.23; + float n_float_nan = 1.0f / 0.0f; + double n_double = 23.42; + + // create (floating point) JSON numbers + json j_ok(v_ok); + json j_nan(v_nan); + json j_infinity(v_infinity); + json j_float(n_float); + json j_float_nan(n_float_nan); + json j_double(n_double); + + // serialize the JSON numbers + std::cout << j_integer_t << '\n'; + std::cout << j_unsigned_t << '\n'; + std::cout << j_enum << '\n'; + std::cout << j_short << '\n'; + std::cout << j_int << '\n'; + std::cout << j_long << '\n'; + std::cout << j_int_least32_t << '\n'; + std::cout << j_uint8_t << '\n'; + std::cout << j_ok << '\n'; + std::cout << j_nan << '\n'; + std::cout << j_infinity << '\n'; + std::cout << j_float << '\n'; + std::cout << j_float_nan << '\n'; + std::cout << j_double << "\n\n"; + + + // ============= + // boolean types + // ============= + + // create boolean values + json j_truth = true; + json j_falsity = false; + + // serialize the JSON booleans + std::cout << j_truth << '\n'; + std::cout << j_falsity << '\n'; +} diff --git a/doc/examples/basic_json__CompatibleType.link b/doc/examples/basic_json__CompatibleType.link new file mode 100644 index 00000000..a78f01bb --- /dev/null +++ b/doc/examples/basic_json__CompatibleType.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/basic_json__CompatibleType.output b/doc/examples/basic_json__CompatibleType.output new file mode 100644 index 00000000..d69ff44f --- /dev/null +++ b/doc/examples/basic_json__CompatibleType.output @@ -0,0 +1,38 @@ +{"one":1,"two":2} +{"one":1,"three":3,"two":2} +{"one":1.2,"three":3.4,"two":2.3} +{"one":true,"three":false,"two":true} +{"one":true,"three":false,"two":true} + +["one","two",3,4.5,false] +[1,2,3,4] +[1.2,2.3,3.4,5.6] +[true,true,false,true] +[12345678909876,23456789098765,34567890987654,45678909876543] +[1,2,3,4] +["four","one","three","two"] +["four","three","two","one"] +["four","one","one","two"] +["four","two","one","one"] + +"The quick brown fox jumps over the lazy dog." +"The quick brown fox jumps over the lazy dog." +"The quick brown fox jumps over the lazy dog." + +-42 +17 +17 +42 +-23 +1024 +-17 +8 +3.14159265358979 +null +null +42.2299995422363 +null +23.42 + +true +false diff --git a/doc/examples/basic_json__array_t.cpp b/doc/examples/basic_json__array_t.cpp deleted file mode 100644 index 1bb6931b..00000000 --- a/doc/examples/basic_json__array_t.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include - -using json = nlohmann::json; - -int main() -{ - // create an array_t value - json::array_t value = {"one", "two", 3, 4.5, false}; - - // create a JSON array from the value - json j(value); - - // serialize the JSON array - std::cout << j << '\n'; -} diff --git a/doc/examples/basic_json__array_t.link b/doc/examples/basic_json__array_t.link deleted file mode 100644 index 70c9cb8c..00000000 --- a/doc/examples/basic_json__array_t.link +++ /dev/null @@ -1 +0,0 @@ -online \ No newline at end of file diff --git a/doc/examples/basic_json__array_t.output b/doc/examples/basic_json__array_t.output deleted file mode 100644 index d379a756..00000000 --- a/doc/examples/basic_json__array_t.output +++ /dev/null @@ -1 +0,0 @@ -["one","two",3,4.5,false] diff --git a/doc/examples/basic_json__boolean_t.cpp b/doc/examples/basic_json__boolean_t.cpp deleted file mode 100644 index 38f014e0..00000000 --- a/doc/examples/basic_json__boolean_t.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include - -using json = nlohmann::json; - -int main() -{ - // create boolean values - json j_truth = true; - json j_falsity = false; - - // serialize the JSON booleans - std::cout << j_truth << '\n'; - std::cout << j_falsity << '\n'; -} diff --git a/doc/examples/basic_json__boolean_t.link b/doc/examples/basic_json__boolean_t.link deleted file mode 100644 index c64e1fc4..00000000 --- a/doc/examples/basic_json__boolean_t.link +++ /dev/null @@ -1 +0,0 @@ -online \ No newline at end of file diff --git a/doc/examples/basic_json__boolean_t.output b/doc/examples/basic_json__boolean_t.output deleted file mode 100644 index da29283a..00000000 --- a/doc/examples/basic_json__boolean_t.output +++ /dev/null @@ -1,2 +0,0 @@ -true -false diff --git a/doc/examples/basic_json__const_int.cpp b/doc/examples/basic_json__const_int.cpp deleted file mode 100644 index 7e38544b..00000000 --- a/doc/examples/basic_json__const_int.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include - -using json = nlohmann::json; - -int main() -{ - // an anonymous enum - enum { t = 17 }; - - // create a JSON number from the enum - json j(t); - - // serialize the JSON numbers - std::cout << j << '\n'; -} diff --git a/doc/examples/basic_json__const_int.link b/doc/examples/basic_json__const_int.link deleted file mode 100644 index 68a9e235..00000000 --- a/doc/examples/basic_json__const_int.link +++ /dev/null @@ -1 +0,0 @@ -online \ No newline at end of file diff --git a/doc/examples/basic_json__const_int.output b/doc/examples/basic_json__const_int.output deleted file mode 100644 index 98d9bcb7..00000000 --- a/doc/examples/basic_json__const_int.output +++ /dev/null @@ -1 +0,0 @@ -17 diff --git a/doc/examples/basic_json__number_float_t.cpp b/doc/examples/basic_json__number_float_t.cpp deleted file mode 100644 index 92533b7d..00000000 --- a/doc/examples/basic_json__number_float_t.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include - -using json = nlohmann::json; - -int main() -{ - // create values of different floating-point types - json::number_float_t v_ok = 3.141592653589793; - json::number_float_t v_nan = NAN; - json::number_float_t v_infinity = INFINITY; - - // create JSON numbers - json j_ok(v_ok); - json j_nan(v_nan); - json j_infinity(v_infinity); - - // serialize the JSON numbers - std::cout << j_ok << '\n'; - std::cout << j_nan << '\n'; - std::cout << j_infinity << '\n'; -} diff --git a/doc/examples/basic_json__number_float_t.link b/doc/examples/basic_json__number_float_t.link deleted file mode 100644 index 47aa2553..00000000 --- a/doc/examples/basic_json__number_float_t.link +++ /dev/null @@ -1 +0,0 @@ -online \ No newline at end of file diff --git a/doc/examples/basic_json__number_float_t.output b/doc/examples/basic_json__number_float_t.output deleted file mode 100644 index 964a7b1f..00000000 --- a/doc/examples/basic_json__number_float_t.output +++ /dev/null @@ -1,3 +0,0 @@ -3.14159265358979 -null -null diff --git a/doc/examples/basic_json__number_integer_t.cpp b/doc/examples/basic_json__number_integer_t.cpp deleted file mode 100644 index 1078f360..00000000 --- a/doc/examples/basic_json__number_integer_t.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include - -using json = nlohmann::json; - -int main() -{ - // create a JSON number from number_integer_t - json::number_integer_t value = 42; - - json j(value); - - // serialize the JSON numbers - std::cout << j << '\n'; -} diff --git a/doc/examples/basic_json__number_integer_t.link b/doc/examples/basic_json__number_integer_t.link deleted file mode 100644 index 5d4499b5..00000000 --- a/doc/examples/basic_json__number_integer_t.link +++ /dev/null @@ -1 +0,0 @@ -online \ No newline at end of file diff --git a/doc/examples/basic_json__number_integer_t.output b/doc/examples/basic_json__number_integer_t.output deleted file mode 100644 index d81cc071..00000000 --- a/doc/examples/basic_json__number_integer_t.output +++ /dev/null @@ -1 +0,0 @@ -42 diff --git a/doc/examples/basic_json__object_t.cpp b/doc/examples/basic_json__object_t.cpp deleted file mode 100644 index 39e2fcc0..00000000 --- a/doc/examples/basic_json__object_t.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include - -using json = nlohmann::json; - -int main() -{ - // create an object_t value - json::object_t value = { {"one", 1}, {"two", 2} }; - - // create a JSON object from the value - json j(value); - - // serialize the JSON object - std::cout << j << '\n'; -} diff --git a/doc/examples/basic_json__object_t.link b/doc/examples/basic_json__object_t.link deleted file mode 100644 index 2e07a3ef..00000000 --- a/doc/examples/basic_json__object_t.link +++ /dev/null @@ -1 +0,0 @@ -online \ No newline at end of file diff --git a/doc/examples/basic_json__object_t.output b/doc/examples/basic_json__object_t.output deleted file mode 100644 index 62376d83..00000000 --- a/doc/examples/basic_json__object_t.output +++ /dev/null @@ -1 +0,0 @@ -{"one":1,"two":2} diff --git a/doc/examples/basic_json__string_t.cpp b/doc/examples/basic_json__string_t.cpp deleted file mode 100644 index 3205f623..00000000 --- a/doc/examples/basic_json__string_t.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include - -using json = nlohmann::json; - -int main() -{ - // create an string_t value - json::string_t value = "The quick brown fox jumps over the lazy doc"; - - // create a JSON string from the value - json j(value); - - // serialize the JSON array - std::cout << j << '\n'; -} diff --git a/doc/examples/basic_json__string_t.link b/doc/examples/basic_json__string_t.link deleted file mode 100644 index d7d02f2f..00000000 --- a/doc/examples/basic_json__string_t.link +++ /dev/null @@ -1 +0,0 @@ -online \ No newline at end of file diff --git a/doc/examples/basic_json__string_t.output b/doc/examples/basic_json__string_t.output deleted file mode 100644 index 89990044..00000000 --- a/doc/examples/basic_json__string_t.output +++ /dev/null @@ -1 +0,0 @@ -"The quick brown fox jumps over the lazy doc" diff --git a/doc/examples/basic_json__string_t_value_type.cpp b/doc/examples/basic_json__string_t_value_type.cpp deleted file mode 100644 index 5379ca06..00000000 --- a/doc/examples/basic_json__string_t_value_type.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include - -using json = nlohmann::json; - -int main() -{ - // create a JSON string directly from a string literal - json j("The quick brown fox jumps over the lazy doc"); - - // serialize the JSON array - std::cout << j << '\n'; -} diff --git a/doc/examples/basic_json__string_t_value_type.link b/doc/examples/basic_json__string_t_value_type.link deleted file mode 100644 index 56908768..00000000 --- a/doc/examples/basic_json__string_t_value_type.link +++ /dev/null @@ -1 +0,0 @@ -online \ No newline at end of file diff --git a/doc/examples/basic_json__string_t_value_type.output b/doc/examples/basic_json__string_t_value_type.output deleted file mode 100644 index 89990044..00000000 --- a/doc/examples/basic_json__string_t_value_type.output +++ /dev/null @@ -1 +0,0 @@ -"The quick brown fox jumps over the lazy doc" diff --git a/src/json.hpp b/src/json.hpp index 2c1fd658..6909553a 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 @@ -107,37 +108,840 @@ SOFTWARE. namespace nlohmann { - /*! @brief unnamed namespace with internal helper functions + +This namespace collects some functions that could not be defined inside the +@ref basic_json class. + +@since version 2.1.0 +*/ +namespace detail +{ +/////////////////////////// +// 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 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 +}; + +/*! +@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)]; +} + + +///////////// +// helpers // +///////////// + +// 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/a/26936864/266378 +template +using is_unscoped_enum = + std::integral_constant::value and + std::is_enum::value>; + +/* +Implementation of two 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 to (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> {}; + + +////////////////// +// constructors // +////////////////// + +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(); + } +}; + + +//////////////////////// +// has_/is_ functions // +//////////////////////// + /*! @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 -{ - private: - template - static int detect(U&&); +#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; \ + } - 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 +{ + 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; +}; + + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json +{ + private: + // 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; +}; + + +///////////// +// to_json // +///////////// + +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); +} + + +/////////////// +// from_json // +/////////////// + +// 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 from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (not 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 (not 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 (not 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 +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 (not 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::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 (not 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::value, int> = 0> +void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +{ + if (not 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::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 +{ + private: + 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, + "could not find to_json() method in T's namespace"); + } + + public: + 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, + "could not find from_json() method in T's namespace"); + } + + 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; +} // namespace detail + + +/// namespace to hold default `to_json` / `from_json` functions +namespace +{ +constexpr const auto& to_json = detail::static_const::value; +constexpr const auto& from_json = detail::static_const::value; +} + + +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @param[in] j JSON value to read from + @param[in, out] val value to write to + */ + template + static void from_json(BasicJsonType&& j, ValueType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward(j), val))) + { + ::nlohmann::from_json(std::forward(j), val); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref basic_json + class. + + @param[in, out] j JSON value to write to + @param[in] val value to read from + */ + template + static void to_json(BasicJsonType& j, ValueType&& 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 @@ -158,6 +962,8 @@ default; will be used in @ref number_integer_t) default; will be used in @ref number_float_t) @tparam AllocatorType type of the allocator to use (`std::allocator` by default) +@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` +and `from_json()` (@ref adl_serializer by default) @requirement The class satisfies the following concept requirements: - Basic @@ -226,21 +1032,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 +1598,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 @@ -1120,18 +1890,6 @@ class basic_json @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) @@ -1165,473 +1923,69 @@ class basic_json } /*! - @brief create an object (explicit) + @brief create a JSON value - Create an object JSON value with a given content. + This is a "catch all" constructor for all compatible JSON types; that is, + types for which a `to_json()` method exsits. The constructor forwards the + parameter @a val to that method (to `json_serializer::to_json` method + with `U = uncvref_t`, to be exact). - @param[in] val a value for the object + Template type @a CompatibleType includes, but is not limited to, the + following types: + - **arrays**: @ref array_t and all kinds of compatible containers such as + `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. + - **objects**: @ref object_t and all kinds of compatible associative + containers such as `std::map`, `std::unordered_map`, `std::multimap`, + and `std::unordered_multimap` with a `key_type` compatible to + @ref string_t and a `value_type` from which a @ref basic_json value can + be constructed. + - **strings**: @ref string_t, string literals, and all compatible string + containers can be used. + - **numbers**: @ref number_integer_t, @ref number_unsigned_t, + @ref number_float_t, and all convertible number types such as `int`, + `size_t`, `int64_t`, `float` or `double` can be used. + - **boolean**: @ref boolean_t / `bool` can be used. - @complexity Linear in the size of the passed @a val. + See the examples below. - @throw std::bad_alloc if allocation for object value fails + @tparam CompatibleType a type such that: + - @a CompatibleType is not derived from `std::istream`, + - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move + constructors), + - @a CompatibleType is not a @ref basic_json nested type (e.g., + @ref json_pointer, @ref iterator, etc ...) + - @ref @ref json_serializer has a + `to_json(basic_json_t&, CompatibleType&&)` method - @liveexample{The following code shows the constructor with an @ref - object_t parameter.,basic_json__object_t} + @tparam U = `uncvref_t` - @sa @ref basic_json(const CompatibleObjectType&) -- create an object value - from a compatible STL container + @param[in] val the value to be forwarded - @since version 1.0.0 - */ - basic_json(const object_t& val) - : m_type(value_t::object), m_value(val) - { - assert_invariant(); - } + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. - /*! - @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 + @throw what `json_serializer::to_json()` throws @liveexample{The following code shows the constructor with several - compatible object type parameters.,basic_json__CompatibleObjectType} + compatible types.,basic_json__CompatibleType} - @sa @ref basic_json(const object_t&) -- create an object value - - @since version 1.0.0 + @since version 2.1.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(); - } - - /*! - @brief create an unsigned number (implicit) - - 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. - - @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`. - - @param[in] val an unsigned integer to create a JSON number from - - @complexity Constant. - - @sa @ref basic_json(const number_unsigned_t) -- create a number value - (unsigned) - - @since version 2.0.0 - */ - template::value and - std::numeric_limits::is_integer and - 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)) - { - 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 - - @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. - - @liveexample{The following example creates several floating-point - values.,basic_json__number_float_t} - - @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number - value (floating-point) from a compatible number type - - @since version 1.0.0 - */ - basic_json(const number_float_t val) noexcept - : m_type(value_t::number_float), m_value(val) - { - // 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. - - @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. - - @liveexample{The example below shows the construction of several - floating-point number values from compatible - types.,basic_json__CompatibleNumberFloatType} - - @sa @ref basic_json(const number_float_t) -- create a number value - (floating-point) - - @since version 1.0.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(CompatibleType && val) noexcept(noexcept(JSONSerializer::to_json( + std::declval(), std::forward(val)))) { + JSONSerializer::to_json(*this, std::forward(val)); assert_invariant(); } @@ -2655,142 +3009,6 @@ class basic_json // 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,26 +3136,63 @@ class basic_json } public: - /// @name value access /// Direct access to the stored value of a JSON value. /// @{ + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template < + typename BasicJsonType, + detail::enable_if_t::type, + basic_json_t>::value, + int> = 0 > + basic_json get() const + { + return *this; + } + /*! @brief get a value (explicit) - Explicit type conversion between the JSON value and a compatible value. + Explicit type conversion between the JSON value and a compatible value + which is [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. - @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 + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + @endcode - @return copy of the JSON value, converted to type @a ValueType + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const @ref basic_json&, ValueType&)`, and + - @ref json_serializer does not have a `from_json()` method of + the form `ValueType from_json(const @ref basic_json&)` - @throw std::domain_error in case passed type @a ValueType is incompatible - to JSON; example: `"type must be object, but is null"` + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type - @complexity Linear in the size of the JSON value. + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can @@ -2946,21 +3201,75 @@ class basic_json associative containers such as `std::unordered_map`.,get__ValueType_const} - @internal - The idea of using a casted null pointer to choose the correct - implementation is from . - @endinternal - - @sa @ref operator ValueType() const for implicit conversion - @sa @ref get() for pointer-member access - - @since version 1.0.0 + @since version 2.1.0 */ - template::value, int>::type = 0> - ValueType get() const + template < + typename ValueTypeCV, + typename ValueType = 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 > + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), std::declval()))) { - return get_impl(static_cast(nullptr)); + // we cannot static_assert on ValueTypeCV 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()"); + + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + } + + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer has a `from_json()` method of the form + `ValueType from_json(const @ref basic_json&)` + + @note If @ref json_serializer has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @since version 2.1.0 + */ + template < + typename ValueTypeCV, + typename ValueType = detail::uncvref_t, + detail::enable_if_t::value and + detail::has_non_default_from_json::value, int> = 0 > + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval()))) + { + 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,7 +5837,7 @@ class basic_json /// @} - + public: ////////////////////////////////////////// // lexicographical comparison operators // ////////////////////////////////////////// @@ -5536,40 +5845,6 @@ class basic_json /// @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 @@ -7782,28 +8057,30 @@ class basic_json @complexity Constant. @liveexample{The following code exemplifies `type_name()` for all JSON - types.,typename} + types.,type_name} @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"; + } } } @@ -8155,6 +8432,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 { @@ -8179,16 +8461,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: @@ -8893,7 +9246,7 @@ class basic_json default: { - if (m_it.primitive_iterator == -n) + if (m_it.primitive_iterator.get_value() == -n) { return *m_object; } @@ -10572,7 +10925,7 @@ basic_json_parser_66: for (; curptr < m_cursor; curptr++) { // quickly skip tests if a digit - if (*curptr < '0' || *curptr > '9') + if (*curptr < '0' or* curptr > '9') { if (*curptr == '.') { @@ -11546,6 +11899,18 @@ basic_json_parser_66: } 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 {}; }; @@ -12206,7 +12571,6 @@ basic_json_parser_66: /// @} }; - ///////////// // presets // ///////////// diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index f552a390..b1df2bf7 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 @@ -107,37 +108,840 @@ SOFTWARE. namespace nlohmann { - /*! @brief unnamed namespace with internal helper functions + +This namespace collects some functions that could not be defined inside the +@ref basic_json class. + +@since version 2.1.0 +*/ +namespace detail +{ +/////////////////////////// +// 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 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 +}; + +/*! +@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)]; +} + + +///////////// +// helpers // +///////////// + +// 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/a/26936864/266378 +template +using is_unscoped_enum = + std::integral_constant::value and + std::is_enum::value>; + +/* +Implementation of two 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 to (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> {}; + + +////////////////// +// constructors // +////////////////// + +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(); + } +}; + + +//////////////////////// +// has_/is_ functions // +//////////////////////// + /*! @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 -{ - private: - template - static int detect(U&&); +#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; \ + } - 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 +{ + 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; +}; + + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json +{ + private: + // 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; +}; + + +///////////// +// to_json // +///////////// + +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); +} + + +/////////////// +// from_json // +/////////////// + +// 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 from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (not 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 (not 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 (not 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 +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 (not 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::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 (not 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::value, int> = 0> +void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +{ + if (not 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::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 +{ + private: + 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, + "could not find to_json() method in T's namespace"); + } + + public: + 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, + "could not find from_json() method in T's namespace"); + } + + 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; +} // namespace detail + + +/// namespace to hold default `to_json` / `from_json` functions +namespace +{ +constexpr const auto& to_json = detail::static_const::value; +constexpr const auto& from_json = detail::static_const::value; +} + + +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @param[in] j JSON value to read from + @param[in, out] val value to write to + */ + template + static void from_json(BasicJsonType&& j, ValueType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward(j), val))) + { + ::nlohmann::from_json(std::forward(j), val); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref basic_json + class. + + @param[in, out] j JSON value to write to + @param[in] val value to read from + */ + template + static void to_json(BasicJsonType& j, ValueType&& 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 @@ -158,6 +962,8 @@ default; will be used in @ref number_integer_t) default; will be used in @ref number_float_t) @tparam AllocatorType type of the allocator to use (`std::allocator` by default) +@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` +and `from_json()` (@ref adl_serializer by default) @requirement The class satisfies the following concept requirements: - Basic @@ -226,21 +1032,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 +1598,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 @@ -1120,18 +1890,6 @@ class basic_json @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) @@ -1165,473 +1923,69 @@ class basic_json } /*! - @brief create an object (explicit) + @brief create a JSON value - Create an object JSON value with a given content. + This is a "catch all" constructor for all compatible JSON types; that is, + types for which a `to_json()` method exsits. The constructor forwards the + parameter @a val to that method (to `json_serializer::to_json` method + with `U = uncvref_t`, to be exact). - @param[in] val a value for the object + Template type @a CompatibleType includes, but is not limited to, the + following types: + - **arrays**: @ref array_t and all kinds of compatible containers such as + `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. + - **objects**: @ref object_t and all kinds of compatible associative + containers such as `std::map`, `std::unordered_map`, `std::multimap`, + and `std::unordered_multimap` with a `key_type` compatible to + @ref string_t and a `value_type` from which a @ref basic_json value can + be constructed. + - **strings**: @ref string_t, string literals, and all compatible string + containers can be used. + - **numbers**: @ref number_integer_t, @ref number_unsigned_t, + @ref number_float_t, and all convertible number types such as `int`, + `size_t`, `int64_t`, `float` or `double` can be used. + - **boolean**: @ref boolean_t / `bool` can be used. - @complexity Linear in the size of the passed @a val. + See the examples below. - @throw std::bad_alloc if allocation for object value fails + @tparam CompatibleType a type such that: + - @a CompatibleType is not derived from `std::istream`, + - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move + constructors), + - @a CompatibleType is not a @ref basic_json nested type (e.g., + @ref json_pointer, @ref iterator, etc ...) + - @ref @ref json_serializer has a + `to_json(basic_json_t&, CompatibleType&&)` method - @liveexample{The following code shows the constructor with an @ref - object_t parameter.,basic_json__object_t} + @tparam U = `uncvref_t` - @sa @ref basic_json(const CompatibleObjectType&) -- create an object value - from a compatible STL container + @param[in] val the value to be forwarded - @since version 1.0.0 - */ - basic_json(const object_t& val) - : m_type(value_t::object), m_value(val) - { - assert_invariant(); - } + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. - /*! - @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 + @throw what `json_serializer::to_json()` throws @liveexample{The following code shows the constructor with several - compatible object type parameters.,basic_json__CompatibleObjectType} + compatible types.,basic_json__CompatibleType} - @sa @ref basic_json(const object_t&) -- create an object value - - @since version 1.0.0 + @since version 2.1.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(); - } - - /*! - @brief create an unsigned number (implicit) - - 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. - - @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`. - - @param[in] val an unsigned integer to create a JSON number from - - @complexity Constant. - - @sa @ref basic_json(const number_unsigned_t) -- create a number value - (unsigned) - - @since version 2.0.0 - */ - template::value and - std::numeric_limits::is_integer and - 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)) - { - 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 - - @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. - - @liveexample{The following example creates several floating-point - values.,basic_json__number_float_t} - - @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number - value (floating-point) from a compatible number type - - @since version 1.0.0 - */ - basic_json(const number_float_t val) noexcept - : m_type(value_t::number_float), m_value(val) - { - // 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. - - @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. - - @liveexample{The example below shows the construction of several - floating-point number values from compatible - types.,basic_json__CompatibleNumberFloatType} - - @sa @ref basic_json(const number_float_t) -- create a number value - (floating-point) - - @since version 1.0.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(CompatibleType && val) noexcept(noexcept(JSONSerializer::to_json( + std::declval(), std::forward(val)))) { + JSONSerializer::to_json(*this, std::forward(val)); assert_invariant(); } @@ -2655,142 +3009,6 @@ class basic_json // 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,26 +3136,63 @@ class basic_json } public: - /// @name value access /// Direct access to the stored value of a JSON value. /// @{ + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template < + typename BasicJsonType, + detail::enable_if_t::type, + basic_json_t>::value, + int> = 0 > + basic_json get() const + { + return *this; + } + /*! @brief get a value (explicit) - Explicit type conversion between the JSON value and a compatible value. + Explicit type conversion between the JSON value and a compatible value + which is [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. - @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 + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + @endcode - @return copy of the JSON value, converted to type @a ValueType + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const @ref basic_json&, ValueType&)`, and + - @ref json_serializer does not have a `from_json()` method of + the form `ValueType from_json(const @ref basic_json&)` - @throw std::domain_error in case passed type @a ValueType is incompatible - to JSON; example: `"type must be object, but is null"` + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type - @complexity Linear in the size of the JSON value. + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can @@ -2946,21 +3201,75 @@ class basic_json associative containers such as `std::unordered_map`.,get__ValueType_const} - @internal - The idea of using a casted null pointer to choose the correct - implementation is from . - @endinternal - - @sa @ref operator ValueType() const for implicit conversion - @sa @ref get() for pointer-member access - - @since version 1.0.0 + @since version 2.1.0 */ - template::value, int>::type = 0> - ValueType get() const + template < + typename ValueTypeCV, + typename ValueType = 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 > + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), std::declval()))) { - return get_impl(static_cast(nullptr)); + // we cannot static_assert on ValueTypeCV 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()"); + + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + } + + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer has a `from_json()` method of the form + `ValueType from_json(const @ref basic_json&)` + + @note If @ref json_serializer has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @since version 2.1.0 + */ + template < + typename ValueTypeCV, + typename ValueType = detail::uncvref_t, + detail::enable_if_t::value and + detail::has_non_default_from_json::value, int> = 0 > + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval()))) + { + 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,7 +5837,7 @@ class basic_json /// @} - + public: ////////////////////////////////////////// // lexicographical comparison operators // ////////////////////////////////////////// @@ -5536,40 +5845,6 @@ class basic_json /// @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 @@ -7782,28 +8057,30 @@ class basic_json @complexity Constant. @liveexample{The following code exemplifies `type_name()` for all JSON - types.,typename} + types.,type_name} @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"; + } } } @@ -8155,6 +8432,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 { @@ -8179,16 +8461,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: @@ -8893,7 +9246,7 @@ class basic_json default: { - if (m_it.primitive_iterator == -n) + if (m_it.primitive_iterator.get_value() == -n) { return *m_object; } @@ -9722,7 +10075,7 @@ class basic_json for (; curptr < m_cursor; curptr++) { // quickly skip tests if a digit - if (*curptr < '0' || *curptr > '9') + if (*curptr < '0' or* curptr > '9') { if (*curptr == '.') { @@ -10696,6 +11049,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 {}; }; @@ -11356,7 +11721,6 @@ class basic_json /// @} }; - ///////////// // presets // ///////////// diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 62213ad3..0ceb6bf6 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 13ce7c3f..dba96e1f 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 640bc816..16833cd9 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 33ea610a..71e75df4 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 b82127bb..994f882d 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 00000000..b939db4e --- /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 7cb9169f..6a5e0c64 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 00000000..241336f3 --- /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()); +}