stephenberry/glaze

GitHub: stephenberry/glaze

一款极致高效的 C++ 序列化库,通过编译时反射实现 JSON 及多种格式的零开销读写。

Stars: 2448 | Forks: 219

# Glaze 世界上最快的 JSON 库之一。Glaze 直接从对象内存进行读写,简化了接口并提供了惊人的性能。 支持的格式: - [JSON](https://stephenberry.github.io/glaze/json/) | `glaze/json.hpp` - [BEVE](https://github.com/beve-org/beve) (二进制高效通用编码) | `glaze/beve.hpp` - [CBOR](https://stephenberry.github.io/glaze/cbor/) (简洁二进制对象表示) | `glaze/cbor.hpp` - [CSV](https://stephenberry.github.io/glaze/csv/) (逗号分隔值) | `glaze/csv.hpp` - [MessagePack](https://stephenberry.github.io/glaze/msgpack/) | `glaze/msgpack.hpp` - [Stencil/Mustache](https://stephenberry.github.io/glaze/stencil-mustache/) (字符串插值) | `glaze/stencil/stencil.hpp` - [TOML 1.1](https://stephenberry.github.io/glaze/toml/) (Tom 的明显、最小语言) | `glaze/toml.hpp` - [YAML](https://stephenberry.github.io/glaze/yaml/) | `glaze/yaml.hpp` - [EETF](https://stephenberry.github.io/glaze/EETF/erlang-external-term-format/) (Erlang 外部项格式) | `glaze/eetf.hpp` - [以及更多功能](https://stephenberry.github.io/glaze/) ## 支持 MSVC、Clang 和 GCC 的 C++23 与 C++26 编译时反射! - 读写聚合可初始化结构体,无需编写任何元数据或宏! - 请参阅 [Compiler Explorer 上的示例](https://gcc.godbolt.org/z/T4To5fKfz) ## C++26 P2996 反射支持 Glaze 现已支持 [P2996 "C++26 反射"](https://wg21.link/P2996)。启用后,P2996 解锁了传统编译时反射无法实现的功能: - **非聚合类型** — 带有构造函数、虚函数和继承的类可直接使用 - **自动枚举序列化** — 无需 `glz::meta`,枚举自动序列化为字符串 - **无限结构体成员** — 无 128 个成员的上限 - **私有成员访问** — 反射所有成员,无论访问说明符如何 - **标准化** — 无编译器特定的技巧,基于 `std::meta` 构建 ``` // Classes with constructors — just works with P2996 class User { public: std::string name; int age; User() = default; User(std::string n, int a) : name(std::move(n)), age(a) {} }; User u{"Alice", 30}; auto json = glz::write_json(u).value_or("error"); // {"name":"Alice","age":30} // Enums serialize as strings — no glz::meta required enum class Color { Red, Green, Blue }; Color c = Color::Green; struct reflect_enums_opts : glz::opts { bool reflect_enums = true; }; auto color_json = glz::write(c).value_or("error"); // "Green" ``` 支持的编译器:[GCC 16+](https://gcc.gnu.org/gcc-16/changes.html) (`-std=c++26 -freflection`) 和 [Bloomberg clang-p2996](https://github.com/bloomberg/clang-p2996)。有关设置和详细信息,请参阅 [P2996 文档](https://stephenberry.github.io/glaze/p2996-reflection/)。 ## [📖 文档](https://stephenberry.github.io/glaze/) 请参阅此 README、[Glaze 文档页面](https://stephenberry.github.io/glaze/)或 [docs 文件夹](https://github.com/stephenberry/glaze/tree/main/docs)获取文档。 ## 亮点 - 纯编译时结构体反射 - 用于自定义名称和行为的强大元特化系统 - [C++26 P2996 反射](https://stephenberry.github.io/glaze/p2996-reflection/)支持 — 非聚合类型、自动枚举、无限成员 - JSON [RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259) 合规性及 UTF-8 验证 - 标准 C++ 库支持 - 仅头文件 - 直接内存序列化/反序列化 - 具有常量查找时间和完美哈希的编译时映射 - 用于修改读/写行为的强大包装器 ([Wrappers](https://stephenberry.github.io/glaze/wrappers/)) - 使用您自己的自定义读/写函数 ([Custom Read/Write](#custom-readwrite)) - 以快速灵活的方式 [处理未知键](https://stephenberry.github.io/glaze/unknown-keys/) - 通过 [JSON 指针语法](https://stephenberry.github.io/glaze/json-pointer-syntax/)直接访问内存 - [JMESPath](https://stephenberry.github.io/glaze/JMESPath/) 查询 - 无异常(可使用 `-fno-exceptions` 编译) - 如果您希望使用抛出异常的辅助函数以获得更清晰的语法,请参阅 [Glaze Exceptions](https://stephenberry.github.io/glaze/exceptions/) - 无需运行时类型信息(可使用 `-fno-rtti` 编译) - [JSON Schema 生成](https://stephenberry.github.io/glaze/json-schema/) - [Partial Read](https://stephenberry.github.io/glaze/partial-read/) 和 [Partial Write](https://stephenberry.github.io/glaze/partial-write/) 支持 - [流式 I/O](https://stephenberry.github.io/glaze/streaming/),用于以有界内存读取/写入大文件 - [更多!](#more-features) ## 性能 | Library | Roundtrip Time (s) | Write (MB/s) | Read (MB/s) | | ------------------------------------------------------------ | ------------------ | ------------ | ----------- | | [**Glaze**](https://github.com/stephenberry/glaze) | **1.01** | **1396** | **1200** | | [**simdjson (on demand)**](https://github.com/simdjson/simdjson) | **N/A** | **N/A** | **1163** | | [**yyjson**](https://github.com/ibireme/yyjson) | **1.22** | **1023** | **1106** | | [**reflect_cpp**](https://github.com/getml/reflect-cpp) | **3.15** | **488** | **365** | | [**daw_json_link**](https://github.com/beached/daw_json_link) | **3.29** | **334** | **479** | | [**RapidJSON**](https://github.com/Tencent/rapidjson) | **3.76** | **289** | **416** | | [**json_struct**](https://github.com/jorgen/json_struct) | **5.87** | **178** | **316** | | [**Boost.JSON**](https://boost.org/libs/json) | **5.38** | **198** | **308** | | [**nlohmann**](https://github.com/nlohmann/json) | **15.44** | **86** | **81** | [性能测试代码可在此处获取](https://github.com/stephenberry/json_performance) *性能注意事项:[simdjson](https://github.com/simdjson/simdjson) 和 [yyjson](https://github.com/ibireme/yyjson) 非常出色,但当数据未按预期顺序排列或缺少任何键时,它们会遭遇重大的性能损失(随着文件大小的增加,问题会变得更加严重,因为它们必须重新遍历文档)。* *此外,[simdjson](https://github.com/simdjson/simdjson) 和 [yyjson](https://github.com/ibireme/yyjson) 不支持自动转义字符串处理,因此如果此基准测试中当前未转义的字符串中包含转义符,这些转义符将不会被处理。* [ABC Test](https://github.com/stephenberry/json_performance) 展示了当键未按预期顺序排列时 simdjson 性能如何下降: | Library | Read (MB/s) | | ------------------------------------------------------------ | ----------- | | [**Glaze**](https://github.com/stephenberry/glaze) | **1219** | | [**simdjson (on demand)**](https://github.com/simdjson/simdjson) | **89** | ## 二进制性能 带标签的二进制规范:[BEVE](https://github.com/beve-org/beve) | Metric | Roundtrip Time (s) | Write (MB/s) | Read (MB/s) | | --------------------- | ------------------ | ------------ | ----------- | | Raw performance | **0.42** | **3235** | **2468** | | Equivalent JSON data* | **0.42** | **3547** | **2706** | JSON 大小:670 字节 BEVE 大小:611 字节 *BEVE 比 JSON 打包效率更高,因此传输相同的数据甚至更快。 ## 示例 您的结构体将自动被反射!用户不需要任何元数据。 ``` struct my_struct { int i = 287; double d = 3.14; std::string hello = "Hello World"; std::array arr = { 1, 2, 3 }; std::map map{{"one", 1}, {"two", 2}}; }; ``` **JSON**(美化格式) ``` { "i": 287, "d": 3.14, "hello": "Hello World", "arr": [ 1, 2, 3 ], "map": { "one": 1, "two": 2 } } ``` **写入 JSON** ``` my_struct s{}; std::string buffer = glz::write_json(s).value_or("error"); ``` 或 ``` my_struct s{}; std::string buffer{}; auto ec = glz::write_json(s, buffer); if (ec) { // handle error } ``` **读取 JSON** ``` std::string buffer = R"({"i":287,"d":3.14,"hello":"Hello World","arr":[1,2,3],"map":{"one":1,"two":2}})"; auto s = glz::read_json(buffer); if (s) // check std::expected { s.value(); // s.value() is a my_struct populated from buffer } ``` 或 ``` std::string buffer = R"({"i":287,"d":3.14,"hello":"Hello World","arr":[1,2,3],"map":{"one":1,"two":2}})"; my_struct s{}; auto ec = glz::read_json(s, buffer); // populates s from buffer if (ec) { // handle error } ``` ### 从文件读取/写入 ``` auto ec = glz::read_file_json(obj, "./obj.json", std::string{}); auto ec = glz::write_file_json(obj, "./obj.json", std::string{}); ``` ### 写入流 要以有界内存流式传输到 `std::ostream` 目的地(文件、网络等): ``` #include "glaze/core/ostream_buffer.hpp" std::ofstream file("output.json"); glz::basic_ostream_buffer buffer(file); // Concrete type for performance auto ec = glz::write_json(obj, buffer); ``` 缓冲区在序列化期间增量刷新,从而可以使用固定内存输出任意大的内容。有关缓冲区类型和流概念的详细信息,请参阅 [流式 I/O](https://stephenberry.github.io/glaze/streaming/)。 ### 从流读取 要以有界内存从 `std::istream` 源(文件、网络等)流式读取: ``` #include "glaze/core/istream_buffer.hpp" std::ifstream file("input.json"); glz::basic_istream_buffer buffer(file); my_struct obj; auto ec = glz::read_json(obj, buffer); ``` 缓冲区在解析期间自动重新填充,从而可以使用固定内存读取任意大的输入。有关 NDJSON 处理和其他流式模式的详细信息,请参阅 [流式 I/O](https://stephenberry.github.io/glaze/streaming/)。 ## 编译器/系统支持 - 需要 C++23 - 同时测试了 64 位和 32 位 - 同时支持小端和大端系统 [Actions](https://github.com/stephenberry/glaze/actions) 在 apple、windows 和 linux 上使用 [Clang](https://clang.llvm.org) (18+)、[MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus/) (2022) 和 [GCC](https://gcc.gnu.org) (13+) 进行构建和测试。大端模式通过 QEMU 在 s390x 上进行仿真测试。 ![clang build](https://github.com/stephenberry/glaze/actions/workflows/clang.yml/badge.svg) ![gcc build](https://static.pigsec.cn/wp-content/uploads/repos/2026/03/c9a8a5a9ae235045.svg) ![msvc build](https://static.pigsec.cn/wp-content/uploads/repos/2026/03/8ae4ba0076235052.svg) ### MSVC 编译器标志 Glaze 需要符合 C++ 标准的预处理器,这在使用 MSVC 构建时需要 `/Zc:preprocessor` 标志。 ### SIMD 架构检测 Glaze 使用编译器预定义宏自动检测目标架构并启用特定于平台的 SIMD 优化: | Flag | Detected When | Architecture | |------|--------------|--------------| | `GLZ_USE_SSE2` | `__x86_64__` or `_M_X64` | x86-64 (always has SSE2) | | `GLZ_USE_AVX2` | `__AVX2__` (in addition to x86-64) | x86-64 with AVX2 | | `GLZ_USE_NEON` | `__aarch64__`, `_M_ARM64`, or `__ARM_NEON` | ARM64 / AArch64 | 由于这些是编译器设置的目标架构宏,因此交叉编译可以自动工作(例如,x86 主机为 ARM 交叉编译不会启用 x86 SIMD 路径)。 要禁用 SIMD 优化: ``` set(glaze_DISABLE_SIMD_WHEN_SUPPORTED ON) ``` 这将设置 `GLZ_DISABLE_SIMD` 作为 INTERFACE 编译定义,传播到所有链接到 `glaze::glaze` 的目标。如果不使用 CMake,请在包含 Glaze 头文件之前定义 `GLZ_DISABLE_SIMD`。 ### 禁用强制内联 为了以牺牲峰值性能为代价获得更快的编译速度和更小的二进制文件,请使用 `glaze_DISABLE_ALWAYS_INLINE`: ``` set(glaze_DISABLE_ALWAYS_INLINE ON) ``` ### C++26 P2996 反射 Glaze 支持 [C++26 P2996 反射](https://wg21.link/P2996)作为结构体反射的替代后端。这将传统的 `__PRETTY_FUNCTION__` 解析替换为标准化的反射原语。 ``` set(glaze_ENABLE_REFLECTION26 ON) ``` 需要 [GCC 16+](https://gcc.gnu.org/gcc-16/changes.html) 或 [Bloomberg clang-p2996](https://github.com/bloomberg/clang-p2996) 及以下标志: **GCC 16+:** ``` -std=c++26 -freflection ``` **Bloomberg clang-p2996:** ``` -std=c++26 -freflection -fexpansion-statements -stdlib=libc++ ``` 优势包括无限的结构体成员(传统反射为 128 个)、更清晰的类型名称以及符合未来的 C++ 标准。有关详细信息,请参阅 [P2996 反射](https://stephenberry.github.io/glaze/p2996-reflection/)。 ### 优化级别(嵌入式/大小优化) Glaze 提供优化级别来控制二进制大小和运行时性能之间的权衡。这对于嵌入式系统很有用: ``` auto json = glz::write(obj); auto ec = glz::read(obj, buffer); ``` | Level | Preset | Description | |-------|--------|-------------| | `normal` | (default) | Maximum performance (~278KB lookup tables for integers and floats) | | `size` | `opts_size` | Minimal binary (~400B integer tables, `std::to_chars` for floats, linear search) | 有关完整详细信息,请参阅 [优化级别](https://stephenberry.github.io/glaze/optimization-levels/)。 ## 如何使用 Glaze ### [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html) ``` include(FetchContent) FetchContent_Declare( glaze GIT_REPOSITORY https://github.com/stephenberry/glaze.git GIT_TAG main GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(glaze) target_link_libraries(${PROJECT_NAME} PRIVATE glaze::glaze) ``` ### [Conan](https://conan.io) - 包含在 [Conan Center](https://conan.io/center/) 中 ![Conan Center](https://img.shields.io/conan/v/glaze) ``` find_package(glaze REQUIRED) target_link_libraries(main PRIVATE glaze::glaze) ``` ### [build2](https://build2.org) - 可在 [cppget](https://cppget.org/libglaze) 上获取 ``` import libs = libglaze%lib{glaze} ``` ### Arch Linux - [官方 Arch 仓库](https://archlinux.org/packages/extra/any/glaze/) - AUR git 包:[glaze-git](https://aur.archlinux.org/packages/glaze-git) ### 请参阅此 [示例仓库](https://github.com/stephenberry/glaze_example) 了解如何在新项目中使用 Glaze ## 请参阅 [常见问题解答](https://stephenberry.github.io/glaze/FAQ/) # 显式元数据 如果您想特化您的反射,您可以**选择性地**编写以下代码: ``` template <> struct glz::meta { using T = my_struct; static constexpr auto value = object( &T::i, &T::d, &T::hello, &T::arr, &T::map ); }; ``` ## 局部 Glaze 元数据
aze 还支持在其关联类内的元数据: ``` struct my_struct { int i = 287; double d = 3.14; std::string hello = "Hello World"; std::array arr = { 1, 2, 3 }; std::map map{{"one", 1}, {"two", 2}}; struct glaze { using T = my_struct; static constexpr auto value = glz::object( &T::i, &T::d, &T::hello, &T::arr, &T::map ); }; }; ```
## 自定义键名或未命名类型 定义 Glaze 元数据时,对象将自动反映成员对象指针的非静态名称。但是,如果您想要自定义名称,或者您注册的 lambda 函数或包装器未为您的字段提供名称,则可以在元数据中选择性地添加字段名称。 自定义名称示例: ``` template <> struct glz::meta { using T = my_struct; static constexpr auto value = object( "integer", &T::i, "double", &T::d, "string", &T::hello, "array", &T::arr, "my map", &T::map ); }; ``` ### 使用 `modify` 扩展纯反射 如果您只需要调整几个字段,可以使用 `glz::meta::modify` 将这些更改分层叠加到自动反射的成员上: ``` struct server_status { std::string name; std::string region; uint64_t active_sessions{}; std::optional maintenance; double cpu_percent{}; }; template <> struct glz::meta { static constexpr auto modify = glz::object( "maintenance_alias", [](auto& self) -> auto& { return self.maintenance; }, "cpuPercent", &server_status::cpu_percent ); }; ``` 序列化 ``` server_status status{ .name = "edge-01", .region = "us-east", .active_sessions = 2412, .maintenance = std::string{"scheduled"}, .cpu_percent = 73.5, }; ``` 生成 ``` { "name": "edge-01", "region": "us-east", "active_sessions": 2412, "maintenance": "scheduled", "cpu_percent": 73.5, "maintenance_alias": "scheduled", "cpuPercent": 73.5 } ``` 所有未触及的成员(`name`、`region`、`active_sessions`、`maintenance`、`cpu_percent`)仍然来自纯反射,因此稍后添加或删除成员仍然可以自动工作。只有 `modify` 中提供的额外键会被分层叠加。 # 反射 API Glaze 提供了一个编译时反射 API,可以通过 `glz::meta` 特化进行修改。此反射 API 使用纯反射,除非提供了 `glz::meta` 特化,在这种情况下,默认行为将被开发人员覆盖。 ``` static_assert(glz::reflect::size == 5); // Number of fields static_assert(glz::reflect::keys[0] == "i"); // Access keys ``` ## glz::for_each_field ``` struct test_type { int32_t int1{}; int64_t int2{}; }; test_type var{42, 43}; glz::for_each_field(var, [](auto& field) { field += 1; }); expect(var.int1 == 43); expect(var.int2 == 44); ``` # 自定义读/写 可以通过强大的 `to`/`from` 特化方法实现自定义读取和写入,详见此处:[custom-serialization.md](https://github.com/stephenberry/glaze/blob/main/docs/custom-serialization.md)。但是,这仅适用于用户定义的类型。 对于常见用例或特定成员变量需要特殊读/写的情况,可以使用 [glz::custom](https://github.com/stephenberry/glaze/blob/main/docs/wrappers.md#custom) 注册读/写成员函数、std::functions 或 lambda 函数。
查看示例: ``` struct custom_encoding { uint64_t x{}; std::string y{}; std::array z{}; void read_x(const std::string& s) { x = std::stoi(s); } uint64_t write_x() { return x; } void read_y(const std::string& s) { y = "hello" + s; } auto& write_z() { z[0] = 5; return z; } }; template <> struct glz::meta { using T = custom_encoding; static constexpr auto value = object("x", custom<&T::read_x, &T::write_x>, // "y", custom<&T::read_y, &T::y>, // "z", custom<&T::z, &T::write_z>); }; suite custom_encoding_test = [] { "custom_reading"_test = [] { custom_encoding obj{}; std::string s = R"({"x":"3","y":"world","z":[1,2,3]})"; expect(!glz::read_json(obj, s)); expect(obj.x == 3); expect(obj.y == "helloworld"); expect(obj.z == std::array{1, 2, 3}); }; "custom_writing"_test = [] { custom_encoding obj{}; std::string s = R"({"x":"3","y":"world","z":[1,2,3]})"; expect(!glz::read_json(obj, s)); std::string out{}; expect(not glz::write_json(obj, out)); expect(out == R"({"x":3,"y":"helloworld","z":[5,2,3]})"); }; }; ```
另一个使用 constexpr lambda 的示例: ``` struct custom_buffer_input { std::string str{}; }; template <> struct glz::meta { static constexpr auto read_x = [](custom_buffer_input& s, const std::string& input) { s.str = input; }; static constexpr auto write_x = [](auto& s) -> auto& { return s.str; }; static constexpr auto value = glz::object("str", glz::custom); }; suite custom_lambdas_test = [] { "custom_buffer_input"_test = [] { std::string s = R"({"str":"Hello!"})"; custom_buffer_input obj{}; expect(!glz::read_json(obj, s)); expect(obj.str == "Hello!"); s.clear(); expect(!glz::write_json(obj, s)); expect(s == R"({"str":"Hello!"})"); expect(obj.str == "Hello!"); }; }; ```
### 使用 `glz::custom` 进行错误处理 开发人员可以抛出错误,但对于禁用异常的构建,或者如果希望将错误处理集成到 Glaze 的 `context` 中,自定义 lambda 的最后一个参数可以是 `glz::context&`。这样可以实现与 Glaze 其余部分良好集成的自定义错误处理。
查看示例: ``` struct age_custom_error_obj { int age{}; }; template <> struct glz::meta { using T = age_custom_error_obj; static constexpr auto read_x = [](T& s, int age, glz::context& ctx) { if (age < 21) { ctx.error = glz::error_code::constraint_violated; ctx.custom_error_message = "age too young"; } else { s.age = age; } }; static constexpr auto value = object("age", glz::custom); }; ``` 使用中: ``` age_custom_error_obj obj{}; std::string s = R"({"age":18})"; auto ec = glz::read_json(obj, s); auto err_msg = glz::format_error(ec, s); std::cout << err_msg << '\n'; ``` 控制台输出: ``` 1:10: constraint_violated {"age":18} ^ age too young ```
# 对象映射 使用成员指针(例如 `&T::a`)时,C++ 类结构必须与 JSON 接口匹配。可能需要将具有不同布局的 C++ 类映射到同一对象接口。这是通过注册 lambda 函数而不是成员指针来实现的。 ``` template <> struct glz::meta { static constexpr auto value = object( "i", [](auto&& self) -> auto& { return self.subclass.i; } ); }; ``` 传递给 lambda 函数的 `self` 值将是一个 `Thing` 对象,而 lambda 函数允许我们使子类对对象接口不可见。 Lambda 函数默认复制返回值,因此通常需要 `auto&` 返回类型,以便 glaze 写入内存。 # 值类型 可以将一个类视为底层值,如下所示: ``` struct S { int x{}; }; template <> struct glz::meta { static constexpr auto value{ &S::x }; }; ``` 或使用 lambda: ``` template <> struct glz::meta { static constexpr auto value = [](auto& self) -> auto& { return self.x; }; }; ``` # 读取约束 Glaze 提供了一个包装器来为结构体成员启用复杂的读取约束:`glz::read_constraint`。
查看示例: ``` struct constrained_object { int age{}; std::string name{}; }; template <> struct glz::meta { using T = constrained_object; static constexpr auto limit_age = [](const T&, int age) { return (age >= 0 && age <= 120); }; static constexpr auto limit_name = [](const T&, const std::string& name) { return name.size() <= 8; }; static constexpr auto value = object("age", read_constraint<&T::age, limit_age, "Age out of range">, // "name", read_constraint<&T::name, limit_name, "Name is too long">); }; ``` 对于无效输入(例如 `{"age": -1, "name": "Victor"}`),Glaze 将输出以下格式的错误消息: ``` 1:11: constraint_violated {"age": -1, "name": "Victor"} ^ Age out of range ``` - 成员函数也可以注册为约束。 - 约束 lambda 的第一个字段是父对象,允许用户编写复杂的约束。
# 读取/写入私有字段 通过创建 `glz::meta` 并在您的类中添加 `friend struct glz::meta;` 来序列化和反序列化私有字段。
查看示例: ``` class private_fields_t { private: double cash = 22.0; std::string currency = "$"; friend struct glz::meta; }; template <> struct glz::meta { using T = private_fields_t; static constexpr auto value = object(&T::cash, &T::currency); }; suite private_fields_tests = [] { "private fields"_test = [] { private_fields_t obj{}; std::string buffer{}; expect(not glz::write_json(obj, buffer)); expect(buffer == R"({"cash":22,"currency":"$"})"); buffer = R"({"cash":2200.0, "currency":"¢"})"; expect(not glz::read_json(obj, buffer)); buffer.clear(); expect(not glz::write_json(obj, buffer)); expect(buffer == R"({"cash":2200,"currency":"¢"})"); }; }; ```
# 错误处理 Glaze 可以安全地用于处理不受信任的消息。错误以错误代码的形式返回,通常在 `glz::expected` 中,其行为与 `std::expected` 完全一样。 要生成更有用的错误消息,请调用 `format_error`: ``` auto pe = glz::read_json(obj, buffer); if (pe) { std::string descriptive_error = glz::format_error(pe, buffer); } ``` 此测试用例: ``` {"Hello":"World"x, "color": "red"} ``` 产生此错误: ``` 1:17: expected_comma {"Hello":"World"x, "color": "red"} ^ ``` 表明 x 在此处无效。 ## 读取时消耗的字节数 读取操作返回的 `error_ctx` 类型包含一个 `count` 字段,指示输入缓冲区中的字节位置: ``` std::string buffer = R"({"x":1,"y":2})"; my_struct obj{}; auto ec = glz::read_json(obj, buffer); if (!ec) { // Success: ec.count contains bytes consumed size_t bytes_consumed = ec.count; // bytes_consumed == 13 (entire JSON object) } ``` 这对于以下情况很有用: - **流式传输**:从单个缓冲区读取多个 JSON 值 - **部分解析**:使用 `partial_read` 选项了解解析停止的位置 - **错误诊断**:失败时,`count` 指示发生错误的位置 # 输入缓冲区(空)终止 建议使用非 const `std::string` 作为输入缓冲区,因为这允许 Glaze 通过临时填充来提高性能,并且缓冲区将以空字符结尾。 ## JSON 默认情况下,选项 `null_terminated` 设置为 `true`,并且在解析 JSON 时必须使用以空字符结尾的缓冲区。可以以性能略微下降为代价关闭该选项,这允许使用非空终止的缓冲区: ``` constexpr glz::opts options{.null_terminated = false}; auto ec = glz::read(value, buffer); // read in a non-null terminated buffer ``` ## BEVE BEVE 不需要空终止。这对性能没有影响。 ## CBOR CBOR 不需要空终止。这对性能没有影响。 ## CSV CSV 不需要空终止。这对性能没有影响。 # 类型支持 ## 数组类型 数组类型逻辑上转换为 JSON 数组值。概念用于允许各种容器,甚至用户容器(如果它们匹配标准库接口)。 - `glz::array`(编译时混合类型) - `std::tuple`(编译时混合类型) - `std::array` - `std::vector` - `std::deque` - `std::list` - `std::forward_list` - `std::span` - `std::set` - `std::unordered_set` ## 对象类型 对象类型逻辑上转换为 JSON 对象值,例如映射。像 JSON 一样,Glaze 将对象定义视为无序映射。因此,对象布局的顺序不必与 C++ 中的相同二进制序列匹配。 - `glz::object`(编译时混合类型) - `std::map` - `std::unordered_map` - `std::pair`(在栈存储中启用动态键) ## 变体 - `std::variant` 有关更多信息,请参阅 [变体处理](https://stephenberry.github.io/glaze/variant-handling/)。 ## 可空类型 - `std::unique_ptr` - `std::shared_ptr` - `std::optional` 可空类型可以由有效输入分配,也可以通过 `null` 关键字置空。 ``` std::unique_ptr ptr{}; std::string buffer{}; expect(not glz::write_json(ptr, buffer)); expect(buffer == "null"); expect(not glz::read_json(ptr, "5")); expect(*ptr == 5); buffer.clear(); expect(not glz::write_json(ptr, buffer)); expect(buffer == "5"); expect(not glz::read_json(ptr, "null")); expect(!bool(ptr)); ``` ## 枚举 默认情况下,枚举将以整数形式写入和读取。如果这是所需的行为,则不需要 `glz::meta`。 但是,如果您更喜欢在 JSON 中将枚举用作字符串,可以在 `glz::meta` 中注册它们,如下所示: ``` enum class Color { Red, Green, Blue }; template <> struct glz::meta { using enum Color; static constexpr auto value = enumerate(Red, Green, Blue ); }; ``` 使用中: ``` Color color = Color::Red; std::string buffer{}; glz::write_json(color, buffer); expect(buffer == "\"Red\""); ``` # 带注释的 JSON (JSONC) 支持规范定义的注释:[JSONC](https://github.com/stephenberry/JSONC) 通过 `glz::read_jsonc` 或 `glz::read(...)` 提供注释读取支持。 # 美化 JSON 可以通过编译时选项直接写出格式化的 JSON: ``` auto ec = glz::write(obj, buffer); ``` 或者,可以使用 `glz::prettify_json` 函数格式化 JSON 文本: ``` std::string buffer = R"({"i":287,"d":3.14,"hello":"Hello World","arr":[1,2,3]})"); auto beautiful = glz::prettify_json(buffer); ``` `beautiful` 现在为: ``` { "i": 287, "d": 3.14, "hello": "Hello World", "arr": [ 1, 2, 3 ] } ``` # 最小化 JSON 要写入最小化的 JSON: ``` auto ec = glz::write_json(obj, buffer); // default is minified ``` 要最小化 JSON 文本,请调用: ``` std::string minified = glz::minify_json(buffer); ``` ## 最小化 JSON 读取 如果您希望要求最小化 JSON 或知道您的输入将始终是最小化的,那么您可以通过使用编译时选项 `.minified = true` 获得更高的性能。 ``` auto ec = glz::read(obj, buffer); ``` ## 布尔标志 Glaze 支持注册一组布尔标志,这些标志表现为字符串选项数组: ``` struct flags_t { bool x{ true }; bool y{}; bool z{ true }; }; template <> struct glz::meta { using T = flags_t; static constexpr auto value = flags("x", &T::x, "y", &T::y, "z", &T::z); }; ``` 示例: ``` flags_t s{}; expect(glz::write_json(s) == R"(["x","z"])"); ``` 只有 `"x"` 和 `"z"` 被写出,因为它们为真。读入缓冲区将设置相应的布尔值。 ## 日志记录 JSON 有时您只想尽可能高效地即时写出 JSON 结构。Glaze 提供了类似元组的结构,允许您栈分配结构以高速写出 JSON。这些结构命名为 `glz::obj`(用于对象)和 `glz::arr`(用于数组)。 以下是构建一个对象(其中也包含一个数组)并将其写出的示例。 ``` auto obj = glz::obj{"pi", 3.14, "happy", true, "name", "Stephen", "arr", glz::arr{"Hello", "World", 2}}; std::string s{}; expect(not glz::write_json(obj, s)); expect(s == R"({"pi":3.14,"happy":true,"name":"Stephen","arr":["Hello","World",2]})"); ``` ## 合并 `glz::merge` 允许用户将多个 JSON 对象类型合并为单个对象。 ``` glz::obj o{"pi", 3.141}; std::map map = {{"a", 1}, {"b", 2}, {"c", 3}}; auto merged = glz::merge{o, map}; std::string s{}; glz::write_json(merged, s); // will write out a single, merged object // s is now: {"pi":3.141,"a":0,"b":2,"c":3} ``` ## 通用 JSON 有关 `glz::generic`,请参阅 [通用 JSON](https://stephenberry.github.io/glaze/generic-json/)。 ``` glz::generic json{}; std::string buffer = R"([5,"Hello World",{"pi":3.14}])"; glz::read_json(json, buffer); assert(json[2]["pi"].get() == 3.14); ``` ## 延迟 JSON 有关 `glz::lazy_json`,请参阅 [延迟 JSON](https://stephenberry.github.io/glaze/lazy-json/)。 ``` std::string json = R"({"name":"John","age":30,"city":"New York"})"; auto result = glz::lazy_json(json); if (result) { auto age = (*result)["age"].get(); // Only parses what you access } ``` `glz::lazy_beve` 为 BEVE 二进制格式提供相同的延迟解析功能。请参阅 [延迟 BEVE](https://stephenberry.github.io/glaze/lazy-beve/)。 ## 原始缓冲区性能 Glaze 写入 `std::string` 的速度与写入原始字符缓冲区一样快。如果您的缓冲区中有足够的空间,您可以写入原始缓冲区,如下所示,但不建议这样做。 ``` glz::read_json(obj, buffer); const auto result = glz::write_json(obj, buffer.data()); if (!result) { buffer.resize(result.count); } ``` ### 写入固定大小的缓冲区 所有写入函数返回 `glz::error_ctx`,它同时提供错误信息和字节计数: ``` std::array buffer; auto ec = glz::write_json(my_obj, buffer); if (ec) { if (ec.ec == glz::error_code::buffer_overflow) { // Buffer was too small std::cerr << "Overflow after " << ec.count << " bytes\n"; } return; } // Success: ec.count contains bytes written std::string_view json(buffer.data(), ec.count); ``` `error_ctx` 类型提供: - `if (ec)` - 存在错误时为 true(符合 `std::error_code` 语义) - `ec.count` - 已处理的字节(始终填充,即使出错) - `ec.ec` - 错误代码 - `glz::format_error(ec, buffer)` - 格式化的错误消息 ## 编译时选项 `glz::opts` 结构定义了读/写的默认编译时选项。 您可以调用 `glz::read(...)` 并自定义选项,而不是调用 `glz::read_json(...)`。 例如:`glz::read(...)` 将关闭对未知键的错误提示并简单地跳过项目。 `glz::opts` 还可以在格式之间切换: - `glz::read(...)` -> `glz::read_beve(...)` - `glz::read(...)` -> `glz::read_json(...)` ### 常用编译时选项 `glz::opts` 结构提供默认选项。以下是最常用的: | Option | Default | Description | |--------|---------|-------------| | `format` | `JSON` | Format selector (`JSON`, `BEVE`, `CSV`, `TOML`) | | `null_terminated` | `true` | Whether input buffer is null terminated | | `error_on_unknown_keys` | `true` | Error on unknown JSON keys | | `skip_null_members` | `true` | Skip null values when writing | | `prettify` | `false` | Output formatted JSON | | `minified` | `false` | Require minified input (faster parsing) | | `error_on_missing_keys` | `false` | Require all keys to be present | | `partial_read` | `false` | Exit after reading deepest object | **可继承选项**(默认不在 `glz::opts` 中)可以通过自定义结构添加: ``` struct my_opts : glz::opts { bool validate_skipped = true; // Full validation on skipped values bool append_arrays = true; // Append to arrays instead of replace }; constexpr my_opts opts{}; auto ec = glz::read(obj, buffer); ``` ## JSON 一致性 默认情况下,Glaze 严格符合最新的 JSON 标准,但在两种情况下有相关选项: - `validate_skipped` 此选项在解析时对跳过的值进行完整的 JSON 验证。默认情况下未设置此项,因为值通常在用户不关心它们时被跳过,并且 Glaze 仍然会针对主要问题进行验证。但是,这使得跳过更快,因为它不关心跳过的值是否完全符合 JSON。例如,默认情况下 Glaze 将确保跳过的数字具有所有有效的数字字符,但除非开启 `validate_skipped`,否则它不会验证跳过的数字中是否存在前导零等问题。无论 Glaze 在何处解析要使用的值,它都会进行完全验证。 - `validate_trailing_whitespace` 此选项验证解析文档中的尾随空格。由于 Glaze 解析 C++ 结构体,通常在读取感兴趣的对象后无需继续解析。如果要确保文档的其余部分具有有效的空格,请打开此选项,否则 Glaze 将在解析完感兴趣的内容后忽略该内容。 ## 跳过 确认对象中键的存在以防止错误可能很有用,但该值可能不需要或在 C++ 中不存在。这些情况通过在元数据中注册 `glz::skip` 类型来处理。
查看示例: ``` struct S { int i{}; }; template <> struct glz::meta { static constexpr auto value = object("key_to_skip", skip{}, &S::i); }; ``` ``` std::string buffer = R"({"key_to_skip": [1,2,3], "i": 7})"; S s{}; glz::read_json(s, buffer); // The value [1,2,3] will be skipped expect(s.i == 7); // only the value i will be read into ```
## 隐藏 Glaze 旨在帮助构建通用 API。有时需要将值暴露给 API,但不希望在 JSON 中读入或写出该值。这就是 `glz::hide` 的用例。 `glz::hide` 从 JSON 输出中隐藏值,同时仍允许 API(和 JSON 指针)访问。
``` struct hide_struct { int i = 287; double d = 3.14; std::string hello = "Hello World"; }; template <> struct glz::meta { using T = hide_struct; static constexpr auto value = object(&T::i, // &T::d, // "hello", hide{&T::hello}); }; ``` ``` hide_struct s{}; auto b = glz::write_json(s); expect(b == R"({"i":287,"d":3.14})"); // notice that "hello" is hidden from the output ```
## 带引号的数字 您可以使用 `glz::quoted` 包装器将带引号的 JSON 数字直接解析为 `double`、`int` 等类型。 ``` struct A { double x; std::vector y; }; template <> struct glz::meta { static constexpr auto value = object("x", glz::quoted_num<&A::x>, "y", glz::quoted_num<&A::y>); }; ``` ``` { "x": "3.14", "y": ["1", "2", "3"] } ``` 带引号的 JSON 数字将直接解析为 `double` 和 `std::vector`。`glz::quoted` 函数也适用于嵌套对象和数组。 ## JSON Lines (NDJSON) 支持 Glaze 支持类数组类型(例如 `std::vector` 和 `std::tuple`)的 [JSON Lines](https://jsonlines.org)(或换行符分隔的 JSON)。 ``` std::vector x = { "Hello", "World", "Ice", "Cream" }; std::string s = glz::write_ndjson(x).value_or("error"); auto ec = glz::read_ndjson(x, s); ``` # 更多功能 ### [数据记录器](https://stephenberry.github.io/glaze/recorder/) ### [命令行界面菜单](https://stephenberry.github.io/glaze/cli-menu/) ### [JMESPath](https://stephenberry.github.io/glaze/JMESPath/) - 查询 JSON ### [JSON 包含系统](https://stephenberry.github.io/glaze/json-include/) ### [JSON 指针语法](https://stephenberry.github.io/glaze/json-pointer-syntax/) ### [JSON-RPC 2.0](https://stephenberry.github.io/glaze/rpc/json-rpc/) ### [JSON Schema](https://stephenberry.github.io/glaze/json-schema/) ### [共享库 API](https://stephenberry.github.io/glaze/glaze-interfaces/) ### [流式 I/O](https://stephenberry.github.io/glaze/streaming/) ### [带标签的二进制消息](https://stephenberry.github.io/glaze/binary/) ### [线程池](https://stephenberry.github.io/glaze/thread-pool/) ### [时间追踪性能分析](https://stephenberry.github.io/glaze/time-trace/) - 将性能配置文件输出到 JSON 并使用 [Perfetto](https://ui.perfetto.dev) 进行可视化 ### [包装器](https://stephenberry.github.io/glaze/wrappers/) # 扩展 请参阅 `ext` 目录以获取扩展。 - [Eigen](https://gitlab.com/libeigen/eigen) - [JSON-RPC 2.0](https://stephenberry.github.io/glaze/rpc/json-rpc/) - [命令行界面菜单 (cli_menu)](https://stephenberry.github.io/glaze/cli-menu/) # 许可证 Glaze 根据 MIT 许可证分发,但对嵌入式形式有一个例外:
标签:Bash脚本, BEVE, C++, C++23, C++26, CBOR, CSV, EETF, Header-Only, JSON库, MessagePack, TOML, YAML, 互操作性, 内存处理, 反射, 反序列化, 后端开发, 安全库, 嵌入式开发, 序列化, 开源库, 搜索引擎爬虫, 数据交换格式, 数据擦除, 游戏开发, 编译期反射