
如何用 fmt 格式化输出 nlohmann/json 以及 fmt 11.1.0 之后 format_as 失效的应对方法【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json任务很具体你手里有nlohmann/json的值想把它交给 fmt 的fmt::format/fmt::print直接格式化而不是先手动dump()再拼字符串。本文基于 JSON for Modern C 仓库的文档给出两条真实存在的路线库自带的format_as()定制点对 fmt 10.0.0–11.0.2 生效以及在 fmt 11.1.0 及更新版本上该定制点失效后、文档给出的fmt::formatter特化替代方案。适用前提是库版本 3.13.0 或更新——format_as和std::formatterbasic_json都是在 3.13.0 中加入的。先判断你用的是哪个 fmt 版本这是整篇文章的分水岭文档format_as 与 FAQ给出的版本边界一致fmt 版本format_as()的效果推荐做法10.0.0 – 11.0.2生效fmt::format({}, j)通过参数依赖查找ADL找到format_as并调用直接使用库自带的format_as11.1.0 及以上不生效fmt 把自动拾取的format_as限定为返回算术类型的重载format_as返回std::string因此只是不被使用不会产生编译错误定义自己的fmt::formatter特化见下文 recipe注意失效的表现是静默的代码照样编译通过只是输出不再来自format_as。如果你升级 fmt 到 11.1.0 后发现fmt::format的行为变了例如退化成经过operator ValueType()的歧义或异常路径先核对版本再决定走哪条路线。路线一库自带的 format_asfmt 10.0.0 – 11.0.2format_as的声明为template typename BasicJsonType std::string format_as(const BasicJsonType j);文档给出的可能实现就是一行return j.dump();要点如下均出自 format_as 文档返回值与dump()相同即紧凑序列化无缩进空白要美化输出这条路线做不到需要用下一节的fmt::formatter特化或 C20 的std::formatterbasic_json。它对包含该翻译单元的fmt调用没有任何副作用只有当调用方的翻译单元同时包含fmt并调用fmt::format/fmt::print作用于 JSON 值时才有意义。异常安全强保证——抛异常时不改变任何 JSON 值若 JSON 内存储的字符串不是 UTF-8 编码会抛出type_error.316。文档示例examples/format_as.cpp直接展示了该函数按 ADL 方式被找到#include iostream #include nlohmann/json.hpp using json nlohmann::json; int main() { // create a JSON value json j {{one, 1}, {two, 2}}; // format_as() is found via argument-dependent lookup, the same way // fmt::format/fmt::print would find it auto j_str format_as(j); std::cout j_str std::endl; }文档示例输出见 examples/format_as.output{one:1,two:2}上面是直接调用format_as(j)的写法在你的 fmt 10.0.0–11.0.2 工程里把j作为参数传入fmt::format({}, j)或fmt::print({}, j)fmt 会以同样的 ADL 机制找到它输出即为j.dump()的结果。路线二fmt 11.1.0 之后——定义 fmt::formatter 特化文档明确说库本身不随包附带这份fmt::formatternlohmann::json特化因为那会让fmt成为构建依赖背景见 FAQ 的 Using JSON values withstd::formatorfmt 条目。但这份 recipe 被原样放在库自己的测试工程里通过 CMakeFetchContent拉取真实的 fmt 版本进行编译和运行验证与std::formatterbasic_json保持同步。完整可编译版本见 tests/fmt_formatter/project/main.cpp。recipe 本体文档以formatter_recipe片段引用的部分template struct fmt::formatternlohmann::json { // -1 means compact output (dump()); any value 0 means pretty-printed // output with that many spaces (or indent_char) per level. int indent -1; char indent_char ; constexpr auto parse(format_parse_context ctx) - format_parse_context::iterator { auto it ctx.begin(); const auto end ctx.end(); constexpr auto is_align [](char c) { return c || c || c ^; }; // [[fill] align] - repurposed here to pick a custom indent character if (it ! end it 1 ! end is_align(it[1])) { indent_char *it; it 2; } else if (it ! end is_align(*it)) { it; } // [#] - alternate form, used here to request pretty-printing with a // default indent of 4 (overridden by an explicit width below, if given) if (it ! end *it #) { indent 4; it; } // [width] - repurposed here to pick the indent size; a width without # // implies pretty-printing since an indent otherwise has no meaning if (it ! end *it 1 *it 9) { indent 0; while (it ! end *it 0 *it 9) { indent (indent * 10) (*it - 0); it; } } if (it ! end *it ! }) { throw fmt::format_error(invalid format args for nlohmann::json); } return it; } auto format(const nlohmann::json j, format_context ctx) const { const auto dumped j.dump(indent, indent_char); return fmt::format_to(ctx.out(), {}, dumped); } };使用时需要把这段特化放进包含#include fmt/format.h与#include nlohmann/json.hpp的翻译单元完整 include 见 tests/fmt_formatter/project/main.cpp 开头。格式说明符的语义与std::formatterbasic_json对齐见 std::formatterbasic_json 文档格式串效果{}紧凑输出等价dump(){:#}美化输出缩进 4等价dump(4){:2}美化输出缩进 2等价dump(2)width 单独出现即隐含美化{:#2}等价dump(2){:.#}缩进 4、缩进字符改为.等价dump(4, .)其余任何规格组件如{:x}会抛出fmt::format_error测试工程里专门断言了这一点。验证方式文档给出的验证手段有两类可直接对照库自测的断言方式tests/fmt_formatter/project/main.cpp 的main函数const nlohmann::json j {{foo, 1}, {bar, {1, 2, 3}}}; assert(fmt::format({}, j) j.dump()); assert(fmt::format({:#}, j) j.dump(4)); assert(fmt::format({:2}, j) j.dump(2)); assert(fmt::format({:#2}, j) j.dump(2)); assert(fmt::format({:.#}, j) j.dump(4, .));即每一种格式串的输出都应与对应参数的j.dump(...)相等{:#}对应dump(4)以此类推。构建验证测试工程 tests/fmt_formatter/project/CMakeLists.txt 用FetchContent拉取 json 仓库与 fmt测试中固定GIT_TAG 12.2.0CMake 要求 3.14C17链接nlohmann_json::nlohmann_json与fmt::fmt并在POST_BUILD阶段直接运行可执行文件——断言不成立时构建本身就失败。你本地若已有仓库与 CMake 3.19可以用同样的方式配置该示例工程cmake -S tests/fmt_formatter/project -B build cmake --build buildcmake --build会自动执行POST_BUILD中的测试运行仓库内正式测试链还要求git见 tests/fmt_formatter/CMakeLists.txt。附带排查不定义任何 formatter 时出现歧义重载FAQUsing JSON values withstd::formatorfmt提到一个相关症状如果你没有任何fmt::formatterjson特化在场把 JSON 值直接传给fmt::format/fmt::print会得到 ambiguous-overload 编译错误。原因是 fmt 拾取了basic_json隐式的operator ValueType()转换运算符对应上游 issue #964 与 #958。规避方式是在包含库之前关闭隐式转换#define JSON_USE_IMPLICIT_CONVERSIONS 0 #include nlohmann/json.hpp该宏的默认值是 1开启隐式转换文档同时说明下一个大版本将默认关闭见 JSON_USE_IMPLICIT_CONVERSIONS用 CMake 集成时也可用选项JSON_ImplicitConversions控制。限制与下一步format_as路线只覆盖紧凑输出美化输出、缩进宽度与缩进字符只能来自路线二的fmt::formatter特化或 C20 下的std::formatterbasic_json其前提是标准库提供format且未关闭JSON_HAS_STD_FORMAT宏见 JSON_HAS_STD_FORMAT。两条路线的底层都是dump()因此dump()的异常行为同样适用默认 strict 模式下JSON 内非 UTF-8 字符串会抛type_error.316如需替换而非抛异常使用dump(-1, , false, json::error_handler_t::replace)这类非严格错误处理器FAQ Serializing untrusted or invalid UTF-8 条目。recipe 中的特化模板参数写死为nlohmann::json如果你使用basic_json的其他特化如ordered_json需要按同一逻辑改为对应类型。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考