# Recipes for Common Types The library converts the standard containers, strings, numbers, tuples, `std::optional`, and `std::filesystem::path` (the last two since version 3.12.0, C++17) out of the box, and your own types once you write conversion functions or use the macros (see [Arbitrary type conversions](arbitrary_types.md)). It deliberately does **not** convert smart pointers, `std::variant`, `std::chrono` types, or class hierarchies, because there is more than one reasonable JSON representation for each of them. This page shows a representation for each that you can copy and adapt. All recipes use a specialization of [`adl_serializer`](../api/adl_serializer/index.md), which you may provide for types you do not own, or free `to_json`/`from_json` functions in the namespace of the type. Place them where they are visible before any code that converts the type; see [Why isn't my `to_json` found?](arbitrary_types.md#why-isnt-my-to_json-or-from_json-found). ## Smart pointers A `std::shared_ptr` or `std::unique_ptr` is written as the value it points to, or as `#!json null` for an empty pointer, and read back by allocating a new `T`: ??? example "Example: serialize shared_ptr and unique_ptr" ```cpp #include #include #include #include using json = nlohmann::json; namespace nlohmann { template struct adl_serializer> { static void to_json(json& j, const std::shared_ptr& p) { if (p) { j = *p; } else { j = nullptr; } } static void from_json(const json& j, std::shared_ptr& p) { if (j.is_null()) { p = nullptr; } else { p = std::make_shared(j.get::type>()); } } }; template struct adl_serializer> { static void to_json(json& j, const std::unique_ptr& p) { if (p) { j = *p; } else { j = nullptr; } } static void from_json(const json& j, std::unique_ptr& p) { if (j.is_null()) { p = nullptr; } else { p = std::make_unique(j.get()); } } }; } // namespace nlohmann struct point { int x; int y; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(point, x, y) int main() { // an empty pointer is written as null const std::vector> points = {std::make_shared(point{1, 2}), nullptr}; const json j = points; std::cout << j << '\n'; // reading allocates new objects const auto back = j.get>>(); std::cout << back[0]->x << ", " << back[0]->y << ", " << (back[1] == nullptr) << '\n'; // pointers to const and unique_ptr work as well const auto const_ptr = j[0].get>(); const auto unique = j[0].get>(); std::cout << const_ptr->y << ", " << unique->x << '\n'; } ``` Output: ``` [{"x":1,"y":2},null] 1, 2, 1 2, 1 ``` With this, containers of smart pointers, pointers to `const`, and members of these types in your own structs convert as usual. A pointer to a base class cannot be filled this way, because `T` would have to be created as a derived type; see the next section. Reading creates a new object. Two `shared_ptr`s that pointed to the same object are written twice and read back as two separate objects. ## Class hierarchies !!! tip "Derived classes whose type is known at compile time" If you only need to serialize the data members of a derived class, and the code always knows the concrete type, you do not need a type tag. The [`NLOHMANN_DEFINE_DERIVED_TYPE_*` macros](arbitrary_types.md#simplify-your-life-with-macros) (since version 3.12.0) add the members of the derived class to those of its base class. This section is for the case where the concrete type is only known at run time, such as a `std::unique_ptr` that holds a circle or a rectangle. JSON has no notion of types, so a value in a hierarchy needs a **type tag** that says which derived class to create. Write the tag in the object, and select the class from it when reading: ??? example "Example: serialize a class hierarchy with a type tag" ```cpp #include #include #include #include #include using json = nlohmann::json; struct shape { virtual ~shape() = default; virtual json serialize() const = 0; }; struct circle : shape { double radius; explicit circle(double r) : radius(r) {} json serialize() const override { return {{"type", "circle"}, {"radius", radius}}; } }; struct rectangle : shape { double width; double height; rectangle(double w, double h) : width(w), height(h) {} json serialize() const override { return {{"type", "rectangle"}, {"width", width}, {"height", height}}; } }; namespace nlohmann { template <> struct adl_serializer> { static void to_json(json& j, const std::unique_ptr& p) { j = p ? p->serialize() : json(nullptr); } static void from_json(const json& j, std::unique_ptr& p) { if (j.is_null()) { p.reset(); return; } const auto& type = j.at("type").get_ref(); if (type == "circle") { p = std::make_unique(j.at("radius").get()); } else if (type == "rectangle") { p = std::make_unique(j.at("width").get(), j.at("height").get()); } else { throw std::invalid_argument("unknown shape type: " + type); } } }; } // namespace nlohmann int main() { std::vector> shapes; shapes.push_back(std::make_unique(1.5)); shapes.push_back(std::make_unique(2, 3)); const json j = shapes; std::cout << j << '\n'; // reading creates objects of the derived types const auto back = j.get>>(); std::cout << std::boolalpha; std::cout << "first is a circle: " << (dynamic_cast(back[0].get()) != nullptr) << '\n'; std::cout << "second is a rectangle: " << (dynamic_cast(back[1].get()) != nullptr) << '\n'; // an unknown type tag is reported try { json{{"type", "star"}}.get>(); } catch (const std::invalid_argument& e) { std::cout << e.what() << '\n'; } } ``` Output: ``` [{"radius":1.5,"type":"circle"},{"height":3.0,"type":"rectangle","width":2.0}] first is a circle: true second is a rectangle: true unknown shape type: star ``` A `#!cpp std::vector>` converts to an array of tagged objects and back, creating a `circle` and a `rectangle`. The same works for `std::shared_ptr`. Remember the virtual destructor: without it, deleting a derived object through the base pointer is undefined behavior. ## `std::variant` A variant has the same problem: the alternative must be recorded. If the alternatives are your own types, write a type tag next to their members, exactly as for a hierarchy: ??? example "Example: serialize a std::variant with a type tag" ```cpp #include #include #include #include #include using json = nlohmann::json; namespace geo { struct circ { double radius; }; struct rect { double width; double height; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(circ, radius) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(rect, width, height) using figure = std::variant; void to_json(json& j, const figure& f) { std::visit([&j](const auto & alternative) { j = alternative; }, f); j["type"] = std::holds_alternative(f) ? "circ" : "rect"; } void from_json(const json& j, figure& f) { const auto& type = j.at("type").get_ref(); if (type == "circ") { f = j.get(); } else if (type == "rect") { f = j.get(); } else { throw std::invalid_argument("unknown figure type: " + type); } } } // namespace geo int main() { const std::vector figures = {geo::circ{2}, geo::rect{3, 4}}; const json j = figures; std::cout << j << '\n'; const auto back = j.get>(); std::cout << "alternatives read back: " << back[0].index() << ", " << back[1].index() << '\n'; } ``` Output: ``` [{"radius":2.0,"type":"circ"},{"height":4.0,"type":"rect","width":3.0}] alternatives read back: 0, 1 ``` In the example, the functions are found because `geo` is the namespace of the template arguments of `#!cpp std::variant`; for a variant of only standard types (for example `#!cpp std::variant`) there is no such namespace, and you need an `adl_serializer` specialization or a wrapper type of your own. If the alternatives are JSON-native (numbers, strings, ...), a variant such as `#!cpp std::variant` can instead be written as the plain value, and read by testing the JSON type (`is_number_integer()`, `is_string()`) to choose the alternative. ## `std::chrono` A duration is written as an object with the keys `unit` and `value`, for example `{"unit": "ms", "value": 1500}`, so the JSON value is self-describing. The serializer names the unit of the period (`ns`, `us`, `ms`, `s`, `min`, `h`; extend the list for other periods), and the deserializer rejects a value whose unit differs from the requested duration type instead of silently misreading it: ??? example "Example: serialize durations and system_clock time points" ```cpp #include #include #include #include #include using json = nlohmann::json; // the name of a period; extend the list for the periods you use template const char* unit_name(); template <> const char* unit_name() { return "ns"; } template <> const char* unit_name() { return "us"; } template <> const char* unit_name() { return "ms"; } template <> const char* unit_name>() { return "s"; } template <> const char* unit_name>() { return "min"; } template <> const char* unit_name>() { return "h"; } namespace nlohmann { template struct adl_serializer> { static void to_json(json& j, const std::chrono::duration& d) { j = {{"unit", unit_name()}, {"value", d.count()}}; } static void from_json(const json& j, std::chrono::duration& d) { if (j.at("unit").get() != unit_name()) { throw std::invalid_argument("unexpected unit " + j.at("unit").dump()); } d = std::chrono::duration(j.at("value").get()); } }; template struct adl_serializer> { static void to_json(json& j, const std::chrono::time_point& t) { j = t.time_since_epoch(); } static void from_json(const json& j, std::chrono::time_point& t) { t = std::chrono::time_point(j.get()); } }; } // namespace nlohmann int main() { using namespace std::chrono; std::cout << std::boolalpha; // the unit is part of the JSON value const json timeout = milliseconds(1500); std::cout << "duration: " << timeout << '\n'; std::cout << "as milliseconds: " << timeout.get().count() << '\n'; // reading with a different unit is rejected try { timeout.get(); } catch (const std::invalid_argument& e) { std::cout << "error: " << e.what() << '\n'; } // a system_clock time point is the time since the Unix epoch const time_point t(milliseconds(1700000000123)); const json stamp = t; std::cout << "time point: " << stamp << '\n'; const auto back = stamp.get>(); std::cout << "round trip: " << (back == t) << '\n'; } ``` Output: ``` duration: {"unit":"ms","value":1500} as milliseconds: 1500 error: unexpected unit "ms" time point: {"unit":"ms","value":1700000000123} round trip: true ``` A `system_clock::time_point` with millisecond precision is written as the duration since the Unix epoch, that is, as an object with `unit` and `value`. The time point serializer is limited to `system_clock`, because the epoch of other clocks (for example `steady_clock`) has no meaning outside the running program. If you prefer a text representation such as ISO 8601 (`2023-11-14T22:13:20Z`), write the string with `#!cpp std::format("{:%FT%TZ}", t)` and read it with `std::chrono::parse` (both C++20), or with a date library, in the same two functions. ## See also - [Arbitrary type conversions](arbitrary_types.md) - the conversion mechanism and macros - [Why isn't my `to_json` found?](arbitrary_types.md#why-isnt-my-to_json-or-from_json-found) - [Converting values](conversions.md) - `get`, implicit conversions, and `std::optional`