-
-
Notifications
You must be signed in to change notification settings - Fork 7.4k
conversion from/to user-defined types #435
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
Changes from 90 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 arbitrary types](#arbitrary-types-conversions) | ||
| - [Binary formats (CBOR and MessagePack)](#binary-formats-cbor-and-messagepack) | ||
| - [Supported compilers](#supported-compilers) | ||
| - [License](#license) | ||
|
|
@@ -441,6 +442,218 @@ int vi = jn.get<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 { | ||
| struct person { std::string name; std::string address; int age; }; | ||
| } | ||
| // convert to JSON | ||
| json j; | ||
| ns::person p = createSomeone(); | ||
| j["name"] = p.name; | ||
| j["address"] = p.address; | ||
| j["age"] = p.age; | ||
|
|
||
| // ... | ||
|
|
||
| // convert from JSON | ||
| ns::person p {j["name"].get<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: | ||
|
|
||
| ```cpp | ||
| ns::person p = createPerson(); | ||
| json j = p; | ||
|
|
||
| auto p2 = j.get<ns::person>(); | ||
| assert(p == p2); | ||
| ``` | ||
|
|
||
| #### Basic usage | ||
|
|
||
| To make this work with one of your types, you only need to provide two methods: | ||
|
|
||
| ```cpp | ||
| using nlohmann::json; | ||
|
|
||
| namespace ns { | ||
| void to_json(json& j, person const& p) | ||
| { | ||
| j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}}; | ||
| } | ||
|
|
||
| void from_json(json const& j, person& p) | ||
| { | ||
| p.name = j["name"].get<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, or the library will not be able to locate them (in this example, they are in namespace `ns`, where `person` is defined). | ||
|
Contributor
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.
Owner
Author
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.
Owner
Author
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) | ||
|
|
||
| #### 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**. | ||
| 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` 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 // 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); | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| 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 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! | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| ### Binary formats (CBOR and MessagePack) | ||
|
|
||
|
|
||
| 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 | ||
|
Contributor
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?
Owner
Author
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 | ||
|
|
||
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.
Maybe: "... two functions" instead of "... two methods". Method sounds like a class method, whereas here it really is two free-standing functions.
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.
Good point!