From 02d05057a5fc93b93c59f812793e0fcfd2edcbf0 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 11:17:22 +0100 Subject: [PATCH] docs: macro chooser generates adl_serializer for non-default-constructible types Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/features/arbitrary_types.md | 2 +- docs/mkdocs/docs/js/fig-macros.js | 97 +++++++++++++++----- 2 files changed, 76 insertions(+), 23 deletions(-) diff --git a/docs/mkdocs/docs/features/arbitrary_types.md b/docs/mkdocs/docs/features/arbitrary_types.md index 057a1a700..36538e502 100644 --- a/docs/mkdocs/docs/features/arbitrary_types.md +++ b/docs/mkdocs/docs/features/arbitrary_types.md @@ -188,7 +188,7 @@ To pick the macro for your own type and get code to start from, answer the quest - 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: only `_ONLY_SERIALIZE` works ([non-default-constructible types](#how-can-i-use-get-for-non-default-constructiblenon-copyable-types){ data-nj-mc="non_default" }). +- 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" }). diff --git a/docs/mkdocs/docs/js/fig-macros.js b/docs/mkdocs/docs/js/fig-macros.js index cc067ff08..7093e1736 100644 --- a/docs/mkdocs/docs/js/fig-macros.js +++ b/docs/mkdocs/docs/js/fig-macros.js @@ -5,7 +5,8 @@ intrusive, non_intrusive, derived, with_names, third_party, non_default. The rules follow include/nlohmann/detail/macro_scope.hpp: WITH_NAMES takes "key", member pairs and no empty member list; WITH_DEFAULT and the plain variants default-construct the type in from_json/get; ONLY_SERIALIZE - generates to_json only; DERIVED_TYPE calls the base type's to_json/from_json first; at most 63 members (31 with + generates to_json only; a type that is not default constructible and converts both ways gets no macro but an + adl_serializer specialization whose from_json returns the value; DERIVED_TYPE calls the base type's to_json/from_json first; at most 63 members (31 with WITH_NAMES). The generated programs were compiled and run with clang++ -std=c++11 against include/. */ (function () { "use strict"; @@ -91,14 +92,12 @@ if (st.edit === "no" && st.access === "private") { res.errors.push({ text: "No macro can reach private members from outside the class. Write to_json/from_json by hand using the public interface, for example in a specialization of adl_serializer.", link: "third_party", linkText: "How do I convert third-party types?" }); } - if (st.dir === "both" && st.ctor === "no") { - res.errors.push({ text: "Every macro that generates from_json default-constructs the type. Use Only to JSON, or write the conversion by hand.", link: "non_default", linkText: "Non-default-constructible types" }); - } - if (names && n === 0) { + var adl = st.dir === "both" && st.ctor === "no"; + if (!adl && names && n === 0) { res.errors.push({ text: "The WITH_NAMES macros need at least one key and member pair. Without members there is nothing to rename: choose Member names." }); } var limit = names ? 31 : 63; - if (n > limit) { + if (!adl && n > limit) { res.errors.push({ text: "The macros support at most " + limit + " members" + (names ? " with custom names" : "") + ". Write to_json/from_json by hand for larger types." }); } @@ -122,6 +121,16 @@ if (st.edit === "no" && st.access === "private") return res; + if (adl) { + res.adl = true; + res.intrusive = st.access === "private"; + res.ref = "non_default"; + res.reason = "Every macro that generates from_json default-constructs the type, so none fits. A specialization of adl_serializer returns the value from from_json instead" + + (st.access === "private" ? "; the class befriends it to reach the private members" : "") + "."; + res.place = "Write the specialization in namespace nlohmann, after the class."; + return res; + } + var intrusive = st.access === "private"; var parts = ["NLOHMANN_DEFINE", st.base === "yes" ? "DERIVED_TYPE" : "TYPE", intrusive ? "INTRUSIVE" : "NON_INTRUSIVE"]; if (st.dir === "to") parts.push("ONLY_SERIALIZE"); @@ -171,7 +180,8 @@ var priv = res.intrusive; var names = st.keys === "custom"; var suffix = st.dir === "to" ? "_ONLY_SERIALIZE" : (st.missing === "default" ? "_WITH_DEFAULT" : ""); - var noCtor = st.ctor === "no" && st.members.length > 0; + var adl = !!res.adl; + var noCtor = st.ctor === "no" && (st.members.length > 0 || st.base === "yes"); var L = []; L.push("#include "); L.push("#include "); @@ -218,14 +228,55 @@ if (priv) L.push(" public:"); } if (noCtor) { - var params = st.members.map(function (mb) { return mb.mtype.trim() + " " + mb.name + "_"; }).join(", "); - var inits = st.members.map(function (mb) { return mb.name + "(" + mb.name + "_)"; }).join(", "); - L.push(" " + T + "(" + params + ")"); - L.push(" : " + inits); + var plist = st.members.map(function (mb) { return mb.mtype.trim() + " " + mb.name + "_"; }); + var ilist = st.members.map(function (mb) { return mb.name + "(" + mb.name + "_)"; }); + if (st.base === "yes") { + plist.unshift(B + " base_"); + ilist.unshift(B + "(base_)"); + } + L.push(" " + T + "(" + plist.join(", ") + ")"); + L.push(" : " + ilist.join(", ")); L.push(" {}"); if (priv) L.push(""); } - if (priv) { + if (adl) { + if (priv) { + L.push(" friend struct nlohmann::adl_serializer<" + T + ">;"); + } + L.push("};"); + L.push("} // namespace " + ns); + L.push(""); + L.push("namespace nlohmann"); + L.push("{"); + L.push("template <>"); + L.push("struct adl_serializer<" + ns + "::" + T + ">"); + L.push("{"); + L.push(" // note: the return type is the value, not void, and there is no default construction"); + L.push(" static " + ns + "::" + T + " from_json(const json& j)"); + L.push(" {"); + var args = st.members.map(function (mb) { + var key = names ? cppString(mb.key) : cppString(mb.name); + var ty = mb.mtype.trim(); + return st.missing === "default" + ? "j.value(" + key + ", " + ty + "{})" + : "j.at(" + key + ").get<" + ty + ">()"; + }); + if (st.base === "yes") args.unshift("j.get<" + ns + "::" + B + ">()"); + L.push(" return " + ns + "::" + T + "{" + args.join(", ") + "};"); + L.push(" }"); + L.push(""); + L.push(" // you must provide to_json as well: the specialization replaces the default one"); + L.push(" static void to_json(json& j, const " + ns + "::" + T + "& t)"); + L.push(" {"); + if (st.base === "yes") L.push(" j = static_cast(t);"); + else L.push(" j = json::object();"); + st.members.forEach(function (mb) { + L.push(" j[" + (names ? cppString(mb.key) : cppString(mb.name)) + "] = t." + mb.name + ";"); + }); + L.push(" }"); + L.push("};"); + L.push("} // namespace nlohmann"); + } else if (priv) { L.push(macroCall(res.macro, head, pairs, " ")); L.push("};"); } else { @@ -233,13 +284,15 @@ L.push(""); L.push(macroCall(res.macro, head, pairs, "")); } - L.push("} // namespace " + ns); + if (!adl) L.push("} // namespace " + ns); L.push(""); L.push("int main()"); L.push("{"); var Q = ns + "::" + T; if (noCtor) { - L.push(" " + Q + " p{" + st.members.map(function (mb) { return mb.mtype.trim() + "{}"; }).join(", ") + "};"); + var init = st.members.map(function (mb) { return mb.mtype.trim() + "{}"; }); + if (st.base === "yes") init.unshift(ns + "::" + B + "{}"); + L.push(" " + Q + " p{" + init.join(", ") + "};"); } else { L.push(" " + Q + " p{};"); } @@ -475,7 +528,7 @@ groups.missing.classList.toggle("nj-mc-off", dirTo); setNote("missing", dirTo ? "Not applicable: ONLY_SERIALIZE generates no from_json." : ""); setNote("access", st.edit === "no" && st.access === "private" ? "Private members of a class you cannot edit are out of reach for every macro." : ""); - setNote("ctor", st.ctor === "no" && !dirTo ? "Parsing needs a default constructor; only Only to JSON works." : ""); + setNote("ctor", st.ctor === "no" && !dirTo ? "The macros default-construct the type when parsing: this gives an adl_serializer specialization instead." : ""); baseField.hidden = st.base !== "yes"; mtable.classList.toggle("nj-mc-custom", st.keys === "custom"); keyHead.hidden = st.keys !== "custom"; @@ -483,18 +536,18 @@ var res = decide(st); result.textContent = ""; hints.textContent = ""; - if (res.macro && !res.errors.length) { + if ((res.macro || res.adl) && !res.errors.length) { result.className = "nj-mc-result"; - result.appendChild(el("p", { "class": "nj-mc-label" }, "Recommended macro")); + result.appendChild(el("p", { "class": "nj-mc-label" }, res.adl ? "No macro fits, write a serializer" : "Recommended macro")); var name = el("p", { "class": "nj-mc-macro" }); - name.appendChild(el("code", null, res.macro)); + name.appendChild(el("code", null, res.adl ? "adl_serializer<" + st.type + ">" : res.macro)); result.appendChild(name); var why = el("p", { "class": "nj-mc-why" }, res.reason + " " + res.place); - addLink(why, res.ref, "Reference"); + addLink(why, res.ref, res.adl ? "Details" : "Reference"); result.appendChild(why); } else { result.className = "nj-mc-result nj-mc-bad"; - result.appendChild(el("p", { "class": "nj-mc-label" }, res.macro ? "Cannot generate this macro yet" : "No macro fits")); + result.appendChild(el("p", { "class": "nj-mc-label" }, (res.macro || res.adl) ? "Cannot generate this code yet" : "No macro fits")); res.errors.forEach(function (e) { var p = el("p", { "class": "nj-mc-why" }, e.text); addLink(p, e.link, e.linkText); @@ -504,13 +557,13 @@ res.warnings.forEach(function (w) { hints.appendChild(el("li", null, w)); }); hints.hidden = !res.warnings.length; - var ok = res.macro && !res.errors.length; + var ok = (res.macro || res.adl) && !res.errors.length; codeWrap.hidden = !ok; if (ok) { currentCode = generate(st, res); code.innerHTML = highlight(currentCode); } - root.setAttribute("data-nj-mc-macro", ok ? res.macro : ""); + root.setAttribute("data-nj-mc-macro", ok ? (res.macro || "adl_serializer") : ""); } update(); }