-
-
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 1 commit
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 |
---|---|---|
|
@@ -442,15 +442,20 @@ 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: | ||
Every type can be serialized in JSON, not just STL-containers and scalar types. Usually, you would do something along those lines: | ||
|
||
```cpp | ||
namespace ns { | ||
struct person { std::string name; std::string address; int age; }; | ||
struct person { | ||
std::string name; | ||
std::string address; | ||
int age; | ||
}; | ||
} | ||
|
||
// convert to JSON | ||
json j; | ||
ns::person p = createSomeone(); | ||
|
@@ -461,10 +466,14 @@ j["age"] = p.age; | |
// ... | ||
|
||
// convert from JSON | ||
ns::person p {j["name"].get<std::string>(), j["address"].get<std::string>(), j["age"].get<int>()}; | ||
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.. Hopefully, there's a better way: | ||
It works, but that's quite a lot of boilerplate... Hopefully, there's a better way: | ||
|
||
```cpp | ||
ns::person p = createPerson(); | ||
|
@@ -482,152 +491,143 @@ To make this work with one of your types, you only need to provide two methods: | |
using nlohmann::json; | ||
|
||
namespace ns { | ||
void to_json(json& j, person const& p) | ||
{ | ||
j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}}; | ||
} | ||
|
||
void from_json(json const& j, person& p) | ||
{ | ||
p.name = j["name"].get<std::string>(); | ||
p.address = j["address"].get<std::string>(); | ||
p.age = j["age"].get<int>(); | ||
} | ||
void to_json(json& j, person const& p) { | ||
j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}}; | ||
} | ||
|
||
void from_json(json const& j, person& p) { | ||
p.name = j["name"].get<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. | ||
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, or the library will not be able to locate them (in this example, they are in namespace `ns`, where `person` is defined). | ||
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. About that part, I don't know if we should add a part mentioning that it can work in the global namespace. 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 think about how to formulate this briefly. 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 added a small comment. |
||
* When using `get<your_type>()`, `your_type` **MUST** be DefaultConstructible and CopyConstructible (There is a way to bypass those requirements described later) | ||
* When using `get<your_type>()`, `your_type` **MUST** be [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible) and [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible). (There is a way to bypass those requirements described later.) | ||
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 removed the need for CopyConstructible. This way, some types can be used thanks to copy elision. 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 removed mention of CopyConstructible. |
||
|
||
#### 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)) | ||
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 | ||
} | ||
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`... | ||
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 | ||
} | ||
}; | ||
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**. | ||
You will need to specialize the `adl_serializer` as well, but with a special `from_json` overload: | ||
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; | ||
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; | ||
|
||
private: | ||
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; | ||
} | ||
}; | ||
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; | ||
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.
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. Right... Did I change this? 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 don't know, but just removing |
||
} | ||
}; | ||
} | ||
``` | ||
|
||
#### Can I write my own serializer? (Advanced use) | ||
|
||
Yes. You might want to take a look at `unit-udt.cpp` in the test suite, to see a few examples. | ||
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 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 // if someone tries to use a type bigger than 32, the compiler will complain | ||
{ | ||
template <typename Json> | ||
static void to_json(Json& j, T value) | ||
{ | ||
// we want to use ADL, and call the correct to_json overload | ||
using nlohmann::to_json; // this method is called by adl_serializer, this is where the magic happens | ||
to_json(j, value); | ||
} | ||
|
||
template <typename Json> | ||
static void from_json(const Json& j, T& value) | ||
{ | ||
// same thing here | ||
using nlohmann::from_json; | ||
from_json(j, value); | ||
} | ||
// 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); | ||
} | ||
}; | ||
``` | ||
|
||
|
@@ -637,21 +637,19 @@ Be **very** careful when reimplementing your serializer, you can stack overflow | |
template <typename T, void> | ||
struct bad_serializer | ||
{ | ||
template <typename Json> | ||
static void to_json(Json& j, const T& value) | ||
{ | ||
// this calls Json::json_serializer<T>::to_json(j, value); | ||
// if Json::json_serializer == bad_serializer ... oops! | ||
j = value; | ||
} | ||
|
||
template <typename Json> | ||
static void to_json(const Json& j, T& value) | ||
{ | ||
// this calls Json::json_serializer<T>::from_json(j, value); | ||
// if Json::json_serializer == bad_serializer ... oops! | ||
value = j.template get<T>(); // oops! | ||
} | ||
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! | ||
} | ||
}; | ||
``` | ||
|
||
|
@@ -759,7 +757,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. | ||
|
@@ -787,7 +785,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. | ||
|
@@ -801,6 +799,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. | ||
|
||
|
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 think you talked about replacing 'Hopefully' with 'Fortunately'
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 forgot about that, thanks!
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.
Changed.