# Arbitrary Type 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; }; } // namespace ns 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(), j["address"].get(), j["age"].get() }; ``` 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 auto p2 = j.get(); // 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) { j.at("name").get_to(p.name); j.at("address").get_to(p.address); j.at("age").get_to(p.age); } } // 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()` or `get_to(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). * Those methods **MUST** be available (e.g., proper headers must be included) everywhere you use these conversions. Look at [#1108](https://github.com/nlohmann/json/issues/1108) for errors that may occur otherwise. * When using `get()`, `your_type` **MUST** be [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). (There is a way to bypass this requirement described later.) * In function `from_json`, use function [`at()`](../api/basic_json/at.md) to access the object values rather than `operator[]`. In case a key does not exist, `at` throws an exception that you can handle, whereas `operator[]` exhibits undefined behavior. * You do not need to add serializers or deserializers for STL types like `std::vector`: the library already implements these. * If you control the type, consider defining `to_json`/`from_json` as `friend` functions inside the class ("hidden friends"). Argument-dependent lookup then only finds them for your type, which also avoids a [GCC < 11 compilation error](../home/faq.md#incomplete-detector-type-with-gcc-11). ??? example "Example: serialize a `person` to JSON with `to_json`" ```cpp #include #include using nlohmann::json; namespace ns { // a simple struct to model a person struct person { std::string name; std::string address; int age; }; } // namespace ns namespace ns { void to_json(json& j, const person& p) { j = json{ {"name", p.name}, {"address", p.address}, {"age", p.age} }; } } // namespace ns int main() { ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60}; json j = p; std::cout << j << '\n'; } ``` Output: ```json {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"} ``` ??? example "Example: deserialize a `person` from JSON with `from_json`" ```cpp #include #include using nlohmann::json; namespace ns { // a simple struct to model a person struct person { std::string name; std::string address; int age; }; } // namespace ns namespace ns { void from_json(const json& j, person& p) { j.at("name").get_to(p.name); j.at("address").get_to(p.address); j.at("age").get_to(p.age); } } // namespace ns int main() { json j; j["name"] = "Ned Flanders"; j["address"] = "744 Evergreen Terrace"; j["age"] = 60; auto p = j.get(); std::cout << p.name << " (" << p.age << ") lives in " << p.address << '\n'; } ``` Output: ``` Ned Flanders (60) lives in 744 Evergreen Terrace ``` ## 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 several macros to make your life easier as long as you want to use a JSON object as serialization. The macros are following the naming pattern, and you can choose the macro based on the needed features: - All the macros start with `NLOHMANN_DEFINE`. - If you want a macro for the derived object, use the [`DERIVED_TYPE`](../api/macros/nlohmann_define_derived_type.md) variant, otherwise use `TYPE`. - The `DERIVED_TYPE` variant requires an additional parameter of a base type, which should have the `to_json`/`from_json` functions defined. For instance, with a macro of its own. - If you need access to the private fields use [`INTRUSIVE`](../api/macros/nlohmann_define_type_intrusive.md) variant, otherwise use [`NON_INTRUSIVE`](../api/macros/nlohmann_define_type_non_intrusive.md). - The `INTRUSIVE` macro should be defined **inside** the target class/struct, `NON_INTRUSIVE` should be defined within the same namespace. - If you want to deserialize the incomplete JSONs, use the `WITH_DEFAULTS` variant, which will use the default values for the member variables absent in JSON, the variant without `WITH_DEFAULTS` will raise an exception. - If you do not need deserialization at all and only interested in `to_json` function, you can use the `ONLY_SERIALIZE` variant. - If you want to use the custom JSON names for member variables, use [`WITH_NAMES`](../api/macros/nlohmann_define_type_with_names.md) variant, otherwise the JSON name of the variable will be the same as its regular name. For all the macros, the first parameter is the name of the class/struct. The `DERIVED_TYPE` macros require a second parameter of a base class. All the remaining parameters name the member variables. The `WITH_NAMES` macros require a JSON name before each of the variables. ```mermaid flowchart TD A["choosing a NLOHMANN_DEFINE_* macro"] --> B{{"adding fields to a base class?"}} B -->|"yes"| C["...DERIVED_TYPE..."] B -->|"no"| D["...TYPE..."] C --> E{{"need access to private members?"}} D --> E E -->|"yes"| F["...INTRUSIVE... (used inside the class)"] E -->|"no"| G["...NON_INTRUSIVE... (used in the namespace)"] F --> H{{"only serializing, never parsing back?"}} G --> H H -->|"yes"| I["...ONLY_SERIALIZE"] H -->|"no"| J{{"allow missing keys when parsing?"}} J -->|"yes"| K["...WITH_DEFAULT"] J -->|"no"| L["plain (missing keys throw)"] I --> M{{"need custom JSON key names?"}} K --> M L --> M M -->|"yes"| N["...WITH_NAMES"] M -->|"no"| O["done"] ```

Decision tree for choosing a `NLOHMANN_DEFINE_*` macro: answer the questions from top to bottom; each answer picks one part of the macro name, and the parts are combined in this order. The table below lists the resulting macros.

| Need access to private members | Need only serialization | Allow missing values when de-serializing | macro | |------------------------------------------------------------------|------------------------------------------------------------------|------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| |
:octicons-check-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-x-circle-fill-24:
| [**NLOHMANN_DEFINE_TYPE_INTRUSIVE**](../api/macros/nlohmann_define_type_intrusive.md) | |
:octicons-check-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-check-circle-fill-24:
| [**NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT**](../api/macros/nlohmann_define_type_intrusive.md) | |
:octicons-check-circle-fill-24:
|
:octicons-check-circle-fill-24:
|
:octicons-skip-fill-24:
| [**NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE**](../api/macros/nlohmann_define_type_intrusive.md) | |
:octicons-x-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-x-circle-fill-24:
| [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE**](../api/macros/nlohmann_define_type_non_intrusive.md) | |
:octicons-x-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-check-circle-fill-24:
| [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT**](../api/macros/nlohmann_define_type_non_intrusive.md) | |
:octicons-x-circle-fill-24:
|
:octicons-check-circle-fill-24:
|
:octicons-skip-fill-24:
| [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE**](../api/macros/nlohmann_define_type_non_intrusive.md) |

Macros for classes and structs without a base class. A green check means yes, a red cross means no, and a grey skip sign means the column does not apply, as a macro that only serializes never reads values.

For _derived_ classes and structs, use the following macros | Need access to private members | Need only serialization | Allow missing values when de-serializing | macro | |------------------------------------------------------------------|------------------------------------------------------------------|------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------| |
:octicons-check-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-x-circle-fill-24:
| [**NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE**](../api/macros/nlohmann_define_derived_type.md) | |
:octicons-check-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-check-circle-fill-24:
| [**NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT**](../api/macros/nlohmann_define_derived_type.md) | |
:octicons-check-circle-fill-24:
|
:octicons-check-circle-fill-24:
|
:octicons-skip-fill-24:
| [**NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE**](../api/macros/nlohmann_define_derived_type.md) | |
:octicons-x-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-x-circle-fill-24:
| [**NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE**](../api/macros/nlohmann_define_derived_type.md) | |
:octicons-x-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-check-circle-fill-24:
| [**NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT**](../api/macros/nlohmann_define_derived_type.md) | |
:octicons-x-circle-fill-24:
|
:octicons-check-circle-fill-24:
|
:octicons-skip-fill-24:
| [**NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE**](../api/macros/nlohmann_define_derived_type.md) |

Macros for derived classes and structs, read as Table 1.

!!! info "Implementation limits" - The current macro implementations are 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. - For the `WITH_NAMES` variants the limit is halved to 31 member variables. To pick the macro for your own type and get code to start from, answer the questions below.
- Members are private: use an `INTRUSIVE` macro inside the class ([reference](../api/macros/nlohmann_define_type_intrusive.md){ data-nj-mc="intrusive" }). - All members are public, or you cannot edit the class: use a `NON_INTRUSIVE` macro in the class's namespace ([reference](../api/macros/nlohmann_define_type_non_intrusive.md){ data-nj-mc="non_intrusive" }). - The class adds members to a serializable base class: use a `DERIVED_TYPE` macro with the base class as second argument ([reference](../api/macros/nlohmann_define_derived_type.md){ data-nj-mc="derived" }). - Missing keys should keep their default values: append `_WITH_DEFAULT`; otherwise a missing key throws. - You only convert to JSON: append `_ONLY_SERIALIZE` instead. - JSON keys differ from the member names: append `_WITH_NAMES` and write `"key", member` pairs ([reference](../api/macros/nlohmann_define_type_with_names.md){ data-nj-mc="with_names" }). - Private members of a class you cannot edit: no macro works; write the conversion by hand ([third-party types](#how-do-i-convert-third-party-types){ data-nj-mc="third_party" }). - The type is not default constructible and is converted in both directions: no macro works; specialize `adl_serializer` with a `from_json` that returns the value, or append `_ONLY_SERIALIZE` if you only convert to JSON ([non-default-constructible types](#how-can-i-use-get-for-non-default-constructiblenon-copyable-types){ data-nj-mc="non_default" }).

Macro chooser: each answer selects one part of the macro name; the generated program defines the type, calls the macro and converts a value to JSON and back. Combinations that no macro supports show the alternative instead of code.

??? example "Example: using the `NLOHMANN_DEFINE_TYPE_*` macros" The `to_json`/`from_json` functions for the `person` struct above can be created with: ```cpp namespace ns { NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person, name, address, age) } ``` 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 another example with private members, where `NLOHMANN_DEFINE_TYPE_INTRUSIVE` is needed: ```cpp namespace ns { class address { private: std::string street; int housenumber; int postcode; public: NLOHMANN_DEFINE_TYPE_INTRUSIVE(address, street, housenumber, postcode) }; } ``` 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) }; } ``` !!! warning "Overriding conversions for natively-supported types" The library already provides built-in `to_json`/`from_json` conversions for STL containers such as `std::vector`, `std::array`, and `std::map`. Defining your own free-function `to_json`/`from_json` overload for one of these container types directly (instead of for your own type) can conflict with the built-in overload during overload resolution, producing compiler errors ("no matching overloaded function", "call is ambiguous") that vary by compiler and library version. If you need different conversion behavior for a container type the library already handles, wrap it in your own type (or use `adl_serializer` specialization, as shown [above](#how-do-i-convert-third-party-types) for `boost::optional`) instead of trying to re-specialize `to_json`/`from_json` for the container type itself. !!! warning "Raw C-style arrays" Members declared as raw C-style arrays (e.g., `char buf[1024]`) do not round-trip safely through `NLOHMANN_DEFINE_TYPE_*` macros or the default (de)serializers: `to_json` serializes any `char` array as a JSON *string* (matching the `std::string`-constructible overload), but the `from_json` overload for fixed-size arrays expects a JSON *array* and iterates it element-wise, which fails with a `type_error` when given a string. Use `std::string`, `std::array`, or a manually written `to_json`/`from_json` pair for such members instead. !!! note "Macros and `nlohmann::ordered_json`" The `NLOHMANN_DEFINE_TYPE_*`/`NLOHMANN_DEFINE_DERIVED_TYPE_*` macros are generic over any `basic_json` specialization, including `nlohmann::ordered_json`. Simply use `ordered_json` as the target type and members are serialized in declaration order — no separate macro or extra code is needed. ```cpp namespace ns { NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person, name, address, age) } ns::person p{"Ned Flanders", "744 Evergreen Terrace", 60}; nlohmann::ordered_json j = p; // keys appear in declaration order: name, address, age ``` !!! note "Zero-member types" All 12 `NLOHMANN_DEFINE_TYPE_*`/`NLOHMANN_DEFINE_DERIVED_TYPE_*` macros (excluding the `WITH_NAMES` variants) also accept types with no member variables to serialize, producing/accepting an empty JSON object `{}` (or, for the derived-type macros, just the base class's own JSON representation): ```cpp namespace ns { struct marker { bool operator==(const marker&) const { return true; } NLOHMANN_DEFINE_TYPE_INTRUSIVE(marker) }; } ns::marker m{}; nlohmann::json j = m; // {} ``` !!! note "No macro for non-default-constructible types" There is currently no `NLOHMANN_DEFINE_TYPE_*`-style macro for types that are not [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). This is not an intentional omission of documentation — no such macro exists yet; see [How can I use `get()` for non-default constructible/non-copyable types?](#how-can-i-use-get-for-non-default-constructiblenon-copyable-types) for the manual pattern to use instead. ## How do I convert third-party types? This requires a bit more advanced technique. But first, let us see how this conversion mechanism works: ```mermaid flowchart LR A["construct json j = t, or call j.get() for T"] --> B["JSONSerializer for T: to_json / from_json"] B -->|"default JSONSerializer"| C["adl_serializer for T: to_json / from_json"] C -->|"unqualified call, found via ADL"| D["free to_json(j, t) / from_json(j, t) in T's namespace"] B -->|"user specialization replaces the default"| E["user's adl_serializer specialization for T"] ```

How a conversion between `json` and a type `T` is dispatched: the default `adl_serializer` calls the free `to_json`/`from_json` functions found by argument-dependent lookup, while a user specialization of `adl_serializer` replaces this path.

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](https://en.cppreference.com/w/cpp/language/adl)). It is implemented like this (simplified): ```cpp template 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) NLOHMANN_JSON_NAMESPACE_BEGIN template struct adl_serializer> { static void to_json(json& j, const boost::optional& opt) { if (opt == boost::none) { j = nullptr; } else { j = *opt; // this will call adl_serializer::to_json which will // find the free function to_json in T's namespace! } } static void from_json(const json& j, boost::optional& opt) { if (j.is_null()) { opt = boost::none; } else { opt = j.get(); // same as above, but with // adl_serializer::from_json } } }; NLOHMANN_JSON_NAMESPACE_END ``` !!! tip "`std::optional` needs no serializer" Since version 3.12.0, `std::optional` is supported out of the box when compiling with C++17 (`std::nullopt` is converted to and from `null`). Do not write an `adl_serializer` for it; this pattern is only needed for types such as `boost::optional` or for custom semantics. See [Conversions](conversions.md) and [Omitting a field when serializing `std::optional`](conversions.md#omitting-a-field-when-serializing-stdoptional). !!! note "ABI compatibility" Use [`NLOHMANN_JSON_NAMESPACE_BEGIN`](../api/macros/nlohmann_json_namespace_begin.md) and `NLOHMANN_JSON_NAMESPACE_END` instead of `#!cpp namespace nlohmann { }` in code which may be linked with different versions of this library. ## How can I use `get()` for non-default constructible/non-copyable types? For a type that is not [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible) but is otherwise an ordinary value type, specialize `adl_serializer` with a `from_json` overload that returns the value instead of writing into a reference: ??? example "Example: `get()` for a non-default-constructible type" ```cpp #include #include using nlohmann::json; namespace ns { // a simple struct to model a person (not default constructible) struct person { person(std::string n, std::string a, int aa) : name(std::move(n)), address(std::move(a)), age(aa) {} std::string name; std::string address; int age; }; } // namespace ns namespace nlohmann { template <> struct adl_serializer { static ns::person from_json(const json& j) { return {j.at("name"), j.at("address"), j.at("age")}; } // Here's the catch! You must provide a to_json method! Otherwise, you // will not be able to convert person to json, since you fully // specialized adl_serializer on that type static void to_json(json& j, ns::person p) { j["name"] = p.name; j["address"] = p.address; j["age"] = p.age; } }; } // namespace nlohmann int main() { json j; j["name"] = "Ned Flanders"; j["address"] = "744 Evergreen Terrace"; j["age"] = 60; auto p = j.get(); std::cout << p.name << " (" << p.age << ") lives in " << p.address << '\n'; } ``` Output: ``` Ned Flanders (60) lives in 744 Evergreen Terrace ``` The same technique also works if your type is not copyable, as long as it is [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible): ```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 { // 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()}; } // 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; } }; } ``` ## Why isn't my `to_json` or `from_json` found? The library calls `to_json` and `from_json` without qualification, so the compiler finds them by [argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl) in the namespace of the type, or in the namespaces of its template arguments. The usual reasons for a function that is "not found" are: - **The type is an alias of a standard type.** After `#!cpp using Hash = std::array;` the type *is* `std::array`, which lives in namespace `std`. A `to_json(json&, const Hash&)` in your namespace is never selected. Either give the type its own identity, for example `#!cpp struct Hash : std::array {};` (the function is then found in `Hash`'s namespace), or specialize [`adl_serializer`](#how-do-i-convert-third-party-types). - **The function is in the wrong namespace.** It must be in the namespace of the type (a global type needs a global function), not in the namespace of the code that calls the conversion. Specializations of `adl_serializer` belong in namespace `nlohmann`. - **It is declared too late.** A conversion can only use what has been declared when it is instantiated. If `#!cpp json j = std::vector{...};` appears before the `adl_serializer` specialization, the compiler reports an error such as "explicit specialization of `adl_serializer<...>` after instantiation". Put the specialization in a small header that every file includes **before** it uses the type with `json`. - **It is a template.** For `template struct box`, write `template void to_json(json&, const box&)`, not an overload for one `T`. - **It worked by accident.** Some versions found `to_json` overloads in `nlohmann::detail` through argument-dependent lookup of the base class of `basic_json`. Do not rely on this; it is not part of the interface. For smart pointers, class hierarchies, `std::variant`, and `std::chrono`, see [Recipes for common types](type_recipes.md). ## Why can't I convert to/from `std::any`? `std::any` is intentionally excluded from `get()`/generic conversion support, so `get()` and containers like `std::map` fail to compile by design — there is no way to know, from a `json` value alone, which concrete type to store inside the `std::any`. To work with heterogeneous JSON values, dispatch on the value's type manually and construct the `std::any` (or extract from it) yourself: ```cpp std::any value_to_any(const json& j) { if (j.is_boolean()) { return j.get(); } if (j.is_number_integer()) { return j.get(); } if (j.is_number_float()) { return j.get(); } if (j.is_string()) { return j.get(); } // ... handle other types (arrays, objects) as needed for your use case return {}; } json any_to_json(const std::any& a) { if (a.type() == typeid(bool)) { return std::any_cast(a); } if (a.type() == typeid(int)) { return std::any_cast(a); } if (a.type() == typeid(double)) { return std::any_cast(a); } if (a.type() == typeid(std::string)) { return std::any_cast(a); } return nullptr; } ``` ## Why does serializing a `std::map`/`std::unordered_map` with non-string keys produce an array? A `std::map`/`std::unordered_map` whose key type is not string-like (e.g., `std::map`) cannot be serialized as a JSON object, because JSON object keys must be strings. See [Converting maps with non-string keys](types/index.md#converting-maps-with-non-string-keys) in the types article for what the library does instead. ## Why does `std::wstring` convert or dump incorrectly? The library assumes UTF-8 encoding internally, so `std::wstring` is not supported out of the box — see the FAQ entry on [wide string handling](../home/faq.md#wide-string-handling) for why, and for a UTF-8 conversion recipe. ## 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/tests/src/unit-udt.cpp) in the test suite, to see a few examples. If you write your own serializer, you will 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::type> struct less_than_32_serializer { template 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 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 struct bad_serializer { template static void to_json(BasicJsonType& j, const T& value) { // this calls BasicJsonType::json_serializer::to_json(j, value); // if BasicJsonType::json_serializer == bad_serializer ... oops! j = value; } template static void from_json(const BasicJsonType& j, T& value) { // this calls BasicJsonType::json_serializer::from_json(j, value); // if BasicJsonType::json_serializer == bad_serializer ... oops! value = j.template get(); // oops! } }; ``` ## See also - [Recipes for common types](type_recipes.md) - smart pointers, class hierarchies, `std::variant`, and `std::chrono` - [Converting values](conversions.md) - the general overview of `get`/`get_to` and implicit conversions - [Specializing enum conversion](enum_conversion.md) - map enums to JSON strings instead of integers - [Supported macros](macros.md) - reference for `NLOHMANN_DEFINE_TYPE_*` and related macros - [`adl_serializer`](../api/adl_serializer/index.md) - the default `JSONSerializer` used in the conversion dispatch - [`NLOHMANN_DEFINE_TYPE_INTRUSIVE`](../api/macros/nlohmann_define_type_intrusive.md) - generate the conversion functions inside the type - [`NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`](../api/macros/nlohmann_define_type_non_intrusive.md) - generate the conversion functions outside the type - [`NLOHMANN_DEFINE_TYPE_WITH_NAMES`](../api/macros/nlohmann_define_type_with_names.md) - use JSON keys that differ from the member names - [`NLOHMANN_DEFINE_DERIVED_TYPE`](../api/macros/nlohmann_define_derived_type.md) - generate the conversion functions for derived classes - [`NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md) - map enum values to JSON values - [`get`](../api/basic_json/get.md) - convert a JSON value to a C++ type - [`get_to`](../api/basic_json/get_to.md) - convert a JSON value into an existing variable - [`json_serializer`](../api/basic_json/json_serializer.md) - the `JSONSerializer` template parameter