-
-
Notifications
You must be signed in to change notification settings - Fork 6.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
conversion from/to user-defined types #435
Changes from all commits
b443edf
fe628b5
d54d6bb
877d96c
12b4555
03b391c
4cdc61e
7dc268e
33abccf
837b81d
2bc685f
178441c
23bd2bc
8881944
0d91113
e2dbe7a
9b40197
ee19aca
47bc402
907484f
74bb11d
e5999c6
60e6f82
c0c72b5
1eafac7
f5cb089
8e43d47
3d405c6
7e750ec
1c21c87
d5ee583
aa2679a
be1d3de
034d5ed
d359684
c833b22
6b89785
bbe4064
d257149
a32de3b
f008983
6d427ac
c847e0e
7e6a6f9
4e8089b
317883b
be6b417
b2543e0
b4cea68
5839795
29f9fe6
1f25ec5
3494014
cb3d455
e678c07
d0d8070
e247e01
a9d5ae4
1554baa
b801287
63e4249
f2c71fa
f1482d1
07bc82d
68081cd
794dae8
e60e458
1d87097
af94e71
b56117b
fbac056
3e15b55
447c6a6
1e20887
d566bb8
a6b0282
889b269
708eb96
7f35901
40ba5a8
f997758
7d771c7
37fd20b
ba0b35f
9c6ef74
9f8b270
9f103d1
3857e55
030cf67
250e5bf
daf8dcd
781fd09
50a3f3b
c154f31
4139bb6
ec03c9c
94d9b7b
4d3053c
77bb7af
1305e03
cd9701b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<int>(); | |
// 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<std::string>(), | ||
j["address"].get<std::string>(), | ||
j["age"].get<int>() | ||
}; | ||
``` | ||
|
||
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<std::string>(); | ||
p.address = j["address"].get<std::string>(); | ||
p.age = j["age"].get<int>(); | ||
} | ||
} // 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<your_type>()`, 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>()`, `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 <typename T> | ||
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 <typename T> | ||
struct adl_serializer<boost::optional<T>> { | ||
static void to_json(json& j, const boost::optional<T>& opt) { | ||
if (opt == boost::none) { | ||
j = nullptr; | ||
} else { | ||
j = *opt; // this will call adl_serializer<T>::to_json which will | ||
// find the free function to_json in T's namespace! | ||
} | ||
} | ||
|
||
static void from_json(const json& j, boost::optional<T>& opt) { | ||
if (!j.is_null()) { | ||
opt = j.get<T>(); // same as above, but with | ||
// adl_serializer<T>::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<move_only_type> { | ||
// 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<int>()}; | ||
} | ||
|
||
// 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<typename T, typename SFINAE = typename std::enable_if<sizeof(T) <= 32>::type> | ||
struct less_than_32_serializer { | ||
template <typename BasicJsonType> | ||
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 <typename BasicJsonType> | ||
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 <typename T, void> | ||
struct bad_serializer | ||
{ | ||
template <typename BasicJsonType> | ||
static void to_json(BasicJsonType& j, const T& value) { | ||
// this calls BasicJsonType::json_serializer<T>::to_json(j, value); | ||
// if BasicJsonType::json_serializer == bad_serializer ... oops! | ||
j = value; | ||
} | ||
|
||
template <typename BasicJsonType> | ||
static void to_json(const BasicJsonType& j, T& value) { | ||
// this calls BasicJsonType::json_serializer<T>::from_json(j, value); | ||
// if BasicJsonType::json_serializer == bad_serializer ... oops! | ||
value = j.template get<T>(); // 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:[email protected]) if I forgot someone. | ||
|
||
|
@@ -611,10 +833,11 @@ Thanks a lot for helping out! Please [let me know](mailto:[email protected]) 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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -109,7 +109,7 @@ RECURSIVE = NO | |
EXCLUDE = | ||
EXCLUDE_SYMLINKS = NO | ||
EXCLUDE_PATTERNS = | ||
EXCLUDE_SYMBOLS = nlohmann::anonymous_namespace | ||
EXCLUDE_SYMBOLS = nlohmann::detail | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we bump the version number here too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I shall change the version number once I created a release branch. |
||
EXAMPLE_PATH = examples | ||
EXAMPLE_PATTERNS = | ||
EXAMPLE_RECURSIVE = NO | ||
|
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
t.i
is private hereThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right... Did I change this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know, but just removing
private:
is fine for this example