-
-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
user-defined types ter: clean, refactor basic_json class #423
Changes from all commits
ce0b3fe
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
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: | ||
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 would replace "Hopefully" with "Fortunately". |
||
|
||
```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). | ||
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. Is it possible to define a user type without namespace? If so: please mention this. If not, please describe why. 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. Yes if both are in the global namespace, I'll try to phrase that well |
||
* When using `get<your_type>()`, `your_type` **MUST** be DefaultConstructible and 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. Is there a way the feature is useful even if the user-defined type is not DefaultConstructible or CopyConstructible? If so, what can I do if 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. As I am reading this top to bottom, I now see that you have a section on this further down. Maybe mention this here as well. |
||
|
||
#### 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: | ||
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. If the example works out of the box, please mention this, e.g. "here is a fully-working example", maybe even listing the |
||
|
||
```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. | ||
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. Please add a link to the unit test. |
||
|
||
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) | ||
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. Is this the |
||
* 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 |
---|---|---|
@@ -0,0 +1,24 @@ | ||
#include <json.hpp> | ||
|
||
using json = nlohmann::json; | ||
|
||
int main() | ||
{ | ||
// create JSON values | ||
json j_null; | ||
json j_boolean = true; | ||
json j_number_integer = 17; | ||
json j_number_float = 23.42; | ||
json j_object = {{"one", 1}, {"two", 2}}; | ||
json j_array = {1, 2, 4, 8, 16}; | ||
json j_string = "Hello, world"; | ||
|
||
// call type_name() | ||
std::cout << j_null.type_name() << '\n'; | ||
std::cout << j_boolean.type_name() << '\n'; | ||
std::cout << j_number_integer.type_name() << '\n'; | ||
std::cout << j_number_float.type_name() << '\n'; | ||
std::cout << j_object.type_name() << '\n'; | ||
std::cout << j_array.type_name() << '\n'; | ||
std::cout << j_string.type_name() << '\n'; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<a target="_blank" href="http://melpon.org/wandbox/permlink/V6imubWo6Lkp8gk1"><b>online</b></a> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
null | ||
boolean | ||
number | ||
number | ||
object | ||
array | ||
string |
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.
typo:
..