Skip to content
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

New macros for the named JSON convertor generation #4563

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 54 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -817,14 +817,33 @@ Some important things:

#### Simplify your life with macros


If you just want to serialize/deserialize some structs, the `to_json`/`from_json` functions can be a lot of boilerplate.

There are two macros to make your life easier as long as you (1) want to use a JSON object as serialization and (2) want to use the member variable names as object keys in that object:
There are several macros to make your life easier if you want to use a JSON object as serialization:

- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(name, member1, member2, ...)`
- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(name, "json_member1", member1, "json_member2", member2, ...)`
- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(name, member1, member2, ...)`
- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(name, "json_member1", member1, "json_member2", member2, ...)`
- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(name, member1, member2, ...)`
- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(name, "json_member1", member1, "json_member2", member2, ...)`
- `NLOHMANN_DEFINE_TYPE_INTRUSIVE(name, member1, member2, ...)`
- `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES(name, "json_member1", member1, "json_member2", member2, ...)`
- `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(name, member1, member2, ...)`
- `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(name, "json_member1", member1, "json_member2", member2, ...)`
- `NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(name, member1, member2, ...)`
- `NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(name, "json_member1", member1, "json_member2", member2, ...)`

The explanation for the names is such:

- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(name, member1, member2, ...)` is to be defined inside the namespace of the class/struct to create code for.
- `NLOHMANN_DEFINE_TYPE_INTRUSIVE(name, member1, member2, ...)` is to be defined inside the class/struct to create code for. This macro can also access private members.
- The `NON_INTRUSIVE` macros should be defined inside the namespace of the class/struct to create code for and thus doesn't have access to the private fields, the `INTRUSIVE` is to be defined inside the class/struct.
- The `WITH_DEFAULT` macros should be used when not all fields are required to be present in the JSON, the ones without `WITH_DEFAULTS` will raise an exception if the fields are missing.
- The `ONLY_SERIALIZE` macros should be used if you only want the `to_json` function to be created. This option excludes the `WITH_DEFAULT` variant since it is only applicable to `from_json`.
- The `WITH_NAMES` macros should be used if you want custom names for you JSON fields, the ones without `WITH_NAMES` will use the member names for JSON fields.
- Each of the aforementioned macros has a `DEFINE_DERIVED_TYPE` variant (e.g. `NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE`) which can be used for the child class, with parent that already have `to_json`/`from_json` defined, possibly with a macro of its own.

In both macros, the first parameter is the name of the class/struct, and all remaining parameters name the members.
For all the macros, the first parameter is the name of the class/struct, and all remaining parameters name the members. The `DERIVED` variants require additional second argument for the parent class.

##### Examples

Expand All @@ -836,7 +855,19 @@ namespace ns {
}
```

Here is an example with private members, where `NLOHMANN_DEFINE_TYPE_INTRUSIVE` is needed:
If you want to inherit the `person` struct and add a field to it, it can be done with:

```cpp
namespace ns {
struct person_derived : person {
std:string email;
};

NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(person_derived, person, email)
}
```

Here is an example with private members, where `INTRUSIVE` is needed:

```cpp
namespace ns {
Expand All @@ -852,6 +883,24 @@ namespace ns {
}
```

Or in case if you use some naming convention that you do not want to expose to JSON:

```cpp
namespace ns {
class address {
private:
std::string m_street;
int m_housenumber;
int m_postcode;

public:
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES(address, "street", m_street,
"housenumber", m_housenumber,
"postcode", m_postcode)
};
}
```

#### How do I convert third-party types?

This requires a bit more advanced technique. But first, let's see how this conversion mechanism works:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;
using namespace nlohmann::literals;

namespace ns
{
struct person
{
std::string name;
std::string address;
int age;
};

void to_json(nlohmann::json& nlohmann_json_j, const person& nlohmann_json_t)
{
nlohmann_json_j["json_name"] = nlohmann_json_t.name;
nlohmann_json_j["json_address"] = nlohmann_json_t.address;
nlohmann_json_j["json_age"] = nlohmann_json_t.age;
}

void from_json(const nlohmann::json& nlohmann_json_j, person& nlohmann_json_t)
{
nlohmann_json_t.name = nlohmann_json_j.at("json_name");
nlohmann_json_t.address = nlohmann_json_j.at("json_address");
nlohmann_json_t.age = nlohmann_json_j.at("json_age");
}
} // namespace ns

int main()
{
ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60};

// serialization: person -> json
json j = p;
std::cout << "serialization: " << j << std::endl;

// deserialization: json -> person
json j2 = R"({"json_address": "742 Evergreen Terrace", "json_age": 40, "json_name": "Homer Simpson"})"_json;
auto p2 = j2.template get<ns::person>();

// incomplete deserialization:
json j3 = R"({"json_address": "742 Evergreen Terrace", "json_name": "Maggie Simpson"})"_json;
try
{
auto p3 = j3.template get<ns::person>();
}
catch (const json::exception& e)
{
std::cout << "deserialization failed: " << e.what() << std::endl;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
serialization: {"json_address":"744 Evergreen Terrace","json_age":60,"json_name":"Ned Flanders"}
deserialization failed: [json.exception.out_of_range.403] key 'json_age' not found
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;
using namespace nlohmann::literals;

namespace ns
{
struct person
{
std::string name;

Check notice on line 11 in docs/examples/nlohmann_define_type_non_intrusive_with_names_macro.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

docs/examples/nlohmann_define_type_non_intrusive_with_names_macro.cpp#L11

struct member 'person::name' is never used.
std::string address;

Check notice on line 12 in docs/examples/nlohmann_define_type_non_intrusive_with_names_macro.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

docs/examples/nlohmann_define_type_non_intrusive_with_names_macro.cpp#L12

struct member 'person::address' is never used.
int age;

Check notice on line 13 in docs/examples/nlohmann_define_type_non_intrusive_with_names_macro.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

docs/examples/nlohmann_define_type_non_intrusive_with_names_macro.cpp#L13

struct member 'person::age' is never used.
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(person, "json_name", name, "json_address", address, "json_age", age)
} // namespace ns

int main()
{
ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60};

// serialization: person -> json
json j = p;
std::cout << "serialization: " << j << std::endl;

// deserialization: json -> person
json j2 = R"({"json_address": "742 Evergreen Terrace", "json_age": 40, "json_name": "Homer Simpson"})"_json;
auto p2 = j2.template get<ns::person>();

// incomplete deserialization:
json j3 = R"({"json_address": "742 Evergreen Terrace", "json_name": "Maggie Simpson"})"_json;
try
{
auto p3 = j3.template get<ns::person>();
}
catch (const json::exception& e)
{
std::cout << "deserialization failed: " << e.what() << std::endl;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
serialization: {"json_address":"744 Evergreen Terrace","json_age":60,"json_name":"Ned Flanders"}
deserialization failed: [json.exception.out_of_range.403] key 'json_age' not found
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The base type **must** be already serializable/deserializable.
: name of the base type (class, struct) `type` is derived from

`member` (in)
: name of the member variable to serialize/deserialize; up to 64 members can be given as comma-separated list
: name of the member variable to serialize/deserialize; up to 63 members can be given as comma-separated list

## Default definition

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ parameter is the name of the class/struct, and all remaining parameters name the
: name of the type (class, struct) to serialize/deserialize

`member` (in)
: name of the member variable to serialize/deserialize; up to 64 members can be given as comma-separated list
: name of the member variable to serialize/deserialize; up to 63 members can be given as comma-separated list

## Default definition

Expand All @@ -50,8 +50,8 @@ See examples below for the concrete generated code.

!!! warning "Implementation limits"

- The current implementation is limited to at most 64 member variables. If you want to serialize/deserialize types
with more than 64 member variables, you need to define the `to_json`/`from_json` functions manually.
- The current implementation is limited to at most 63 member variables. If you want to serialize/deserialize types
with more than 63 member variables, you need to define the `to_json`/`from_json` functions manually.
- The macros only work for the [`nlohmann::json`](../json.md) type; other specializations such as
[`nlohmann::ordered_json`](../ordered_json.md) are currently unsupported.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ parameter is the name of the class/struct, and all remaining parameters name the
: name of the type (class, struct) to serialize/deserialize

`member` (in)
: name of the (public) member variable to serialize/deserialize; up to 64 members can be given as comma-separated list
: name of the (public) member variable to serialize/deserialize; up to 63 members can be given as comma-separated list

## Default definition

Expand All @@ -51,8 +51,8 @@ See examples below for the concrete generated code.

!!! warning "Implementation limits"

- The current implementation is limited to at most 64 member variables. If you want to serialize/deserialize types
with more than 64 member variables, you need to define the `to_json`/`from_json` functions manually.
- The current implementation is limited to at most 63 member variables. If you want to serialize/deserialize types
with more than 63 member variables, you need to define the `to_json`/`from_json` functions manually.
- The macros only work for the [`nlohmann::json`](../json.md) type; other specializations such as
[`nlohmann::ordered_json`](../ordered_json.md) are currently unsupported.

Expand Down
73 changes: 73 additions & 0 deletions docs/mkdocs/docs/api/macros/nlohmann_define_type_with_names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# The Named Conversion Macros

```cpp
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES(type, "json_member_name", member...)
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(type, "json_member_name", member...)
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(type, "json_member_name", member...)
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(type, "json_member_name", member...)
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(type, "json_member_name", member...)
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(type, "json_member_name", member...)
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_NAMES(type, base_type, "json_member_name", member...)
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(type, base_type, "json_member_name", member...)
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(type, base_type, "json_member_name", member...)
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_NAMES(type, base_type, "json_member_name", member...)
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(type, base_type, "json_member_name", member...)
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(type, base_type, "json_member_name", member...)
```

These macros can be used in case you want to use the custom names for the member variables in the resulting JSON.
They behave exactly as their non-`WITH_NAMES` counterparts, but require an additional parameter for each member variable
which will be used in JSON. Both serialization and deserialization will only use the custom names for JSON, the names of
the member variables themselves will be ignored.

Using the named conversion macros will halve the maximum number of member variables from 63 to 31.

For further information please refer to the corresponding macros without `WITH_NAMES`.

## Parameters

`type` (in)
: name of the type (class, struct) to serialize/deserialize

`base_type` (in)
: name of the base type (class, struct) `type` is derived from (used only in `DEFINE_DERIVED_TYPE` macros)

`json_member_name` (in)
: used in named conversion macros, must be provided for each member variable and will be used as a member variable name in the resulting json

`member` (in)
: name of the member variable to serialize/deserialize

## Examples

??? example "Example (1): NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE"

Consider the following complete example:

```cpp hl_lines="16"
--8<-- "examples/nlohmann_define_type_non_intrusive_with_names_macro.cpp"
```

Output:

```json
--8<-- "examples/nlohmann_define_type_non_intrusive_with_names_macro.output"
```

Notes:

- `ns::person` is default-constructible. This is a requirement for using the macro.
- `ns::person` has only public member variables. This makes `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES` applicable.
- The macro `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES` is used _outside_ the class, but _inside_ its namespace `ns`.
- A missing key "age" in the deserialization yields an exception. To fall back to the default value,
`NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES` can be used.

The macro is equivalent to:

```cpp hl_lines="16 17 18 19 20 21 22 23 24 25 26 27 28"
--8<-- "examples/nlohmann_define_type_non_intrusive_with_names_explicit.cpp"
```

## Version history

1. Added in version 3.11.x.
14 changes: 14 additions & 0 deletions docs/mkdocs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -288,13 +288,27 @@ nav:
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE': api/macros/nlohmann_define_derived_type.md
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT': api/macros/nlohmann_define_derived_type.md
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_derived_type.md
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_NAMES': api/macros/nlohmann_define_type_with_names.md
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES': api/macros/nlohmann_define_type_with_names.md
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES': api/macros/nlohmann_define_type_with_names.md
- 'NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE': api/macros/nlohmann_define_derived_type.md
- 'NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT': api/macros/nlohmann_define_derived_type.md
- 'NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_derived_type.md
- 'NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_NAMES': api/macros/nlohmann_define_type_with_names.md
- 'NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES': api/macros/nlohmann_define_type_with_names.md
- 'NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES': api/macros/nlohmann_define_type_with_names.md
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE': api/macros/nlohmann_define_type_intrusive.md
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT': api/macros/nlohmann_define_type_intrusive.md
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_intrusive.md
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES': api/macros/nlohmann_define_type_with_names.md
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES': api/macros/nlohmann_define_type_with_names.md
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES': api/macros/nlohmann_define_type_with_names.md
- 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE': api/macros/nlohmann_define_type_non_intrusive.md
- 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT': api/macros/nlohmann_define_type_non_intrusive.md
- 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_non_intrusive.md
- 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES': api/macros/nlohmann_define_type_with_names.md
- 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES': api/macros/nlohmann_define_type_with_names.md
- 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES': api/macros/nlohmann_define_type_with_names.md
- 'NLOHMANN_JSON_NAMESPACE': api/macros/nlohmann_json_namespace.md
- 'NLOHMANN_JSON_NAMESPACE_BEGIN': api/macros/nlohmann_json_namespace_begin.md
- 'NLOHMANN_JSON_NAMESPACE_END': api/macros/nlohmann_json_namespace_begin.md
Expand Down
Loading
Loading