Skip to content

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:

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<std::string>(),
    j["address"].get<std::string>(),
    j["age"].get<int>()
};

It works, but that's quite a lot of boilerplate... Fortunately, there's a better way:

// 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<ns::person>();

// that's it
assert(p == p2);

Basic usage

To make this work with one of your types, you only need to provide two functions:

using json = 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<your_type>() 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 for errors that may occur otherwise.
  • When using get<your_type>(), your_type MUST be DefaultConstructible. (There is a way to bypass this requirement described later.)
  • In function from_json, use function at() 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.

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 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 variant, otherwise use NON_INTRUSIVE.
    • 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 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.

Need access to private members Need only de-serialization Allow missing values when de-serializing macro
NLOHMANN_DEFINE_TYPE_INTRUSIVE
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE

For derived classes and structs, use the following macros

Need access to private members Need only de-serialization Allow missing values when de-serializing macro
NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE
NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT
NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE
NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE
NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT
NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE

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.
Example

The to_json/from_json functions for the person struct above can be created with:

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:

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:

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:

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)
    };
}

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 for boost::optional) instead of trying to re-specialize to_json/from_json for the container type itself.

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<char, N>, or a manually written to_json/from_json pair for such members instead.

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.

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

No macro for non-default-constructible types

There is currently no NLOHMANN_DEFINE_TYPE_*-style macro for types that are not 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? 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:

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).

It is implemented like this (simplified):

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:

// partial specialization (full specialization works too)
NLOHMANN_JSON_NAMESPACE_BEGIN
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 = boost::none;
        } else {
            opt = j.get<T>(); // same as above, but with
                              // adl_serializer<T>::from_json
        }
    }
};
NLOHMANN_JSON_NAMESPACE_END

ABI compatibility

Use NLOHMANN_JSON_NAMESPACE_BEGIN and NLOHMANN_JSON_NAMESPACE_END instead of 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?

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:

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;
        }
    };
}

Why can't I convert to/from std::any?

std::any is intentionally excluded from get<T>()/generic conversion support, so get<std::any>() and containers like std::map<std::string, std::any> 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:

std::any value_to_any(const json& j) {
    if (j.is_boolean())      { return j.get<bool>(); }
    if (j.is_number_integer()) { return j.get<int>(); }
    if (j.is_number_float()) { return j.get<double>(); }
    if (j.is_string())       { return j.get<std::string>(); }
    // ... 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<bool>(a); }
    if (a.type() == typeid(int))         { return std::any_cast<int>(a); }
    if (a.type() == typeid(double))      { return std::any_cast<double>(a); }
    if (a.type() == typeid(std::string)) { return std::any_cast<std::string>(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<int, std::string>) is serialized as a JSON array of 2-element [key, value] arrays, not as a JSON object -- JSON object keys must be strings, so the library cannot represent an integer-keyed map as an object.

std::map<int, std::string> m{{1, "one"}, {2, "two"}};
json j = m;
// j is [[1,"one"],[2,"two"]], not {"1":"one","2":"two"}

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 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 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.

// 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);
    }
};

Be very careful when reimplementing your serializer, you can stack overflow if you don't pay attention:

template <typename T, void>
struct bad_serializer
{
    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 from_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!
    }
};