ITADN
stephenberry/glaze · 文件 下载 ZIP
文件最后提交记录最后更新时间
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

Glaze

全球最快的 JSON 库之一。Glaze 从对象内存中读取和写入,简化了接口并提供卓越的性能。

支持的格式:

  • JSON | glaze/json.hpp
  • BEVE (Binary Efficient Versatile Encoding) | glaze/beve.hpp
  • CBOR (Concise Binary Object Representation) | glaze/cbor.hpp
  • JSONB (SQLite Binary JSON) | glaze/jsonb.hpp
  • BSON (MongoDB Binary JSON) | glaze/bson.hpp
  • CSV (Comma Separated Value) | glaze/csv.hpp
  • MessagePack | glaze/msgpack.hpp
  • Stencil/Mustache (string interpolation) | glaze/stencil/stencil.hpp
  • TOML 1.1 (Tom's Obvious, Minimal Language) | glaze/toml.hpp
  • YAML | glaze/yaml.hpp
  • EETF (Erlang External Term Format) | glaze/eetf.hpp
  • And Many More Features

[!NOTE]

Glaze 正在获得 HTTP 支持,包括 REST 服务器、客户端、websockets 等。Glaze 的网络部分正在积极开发中,虽然它已可用且需要反馈,但 API 可能会发生变化并持续改进。

支持 MSVC、Clang 和 GCC 的 C++23 编译时反射!

C++26 P2996 反射支持

Glaze 现在支持 P2996 "Reflection for C++26"。启用后,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<reflect_enums_opts{}>(c).value_or("error");
// "Green"

支持的编译器:GCC 16+ (-std=c++26 -freflection) 和 Bloomberg clang-p2996。请参阅 P2996 文档 了解设置和详细信息。

📖 文档

请参阅此 README、Glaze 文档页面docs 文件夹 以获取文档。

亮点

  • 针对结构体的纯编译时反射
    • 强大的元特化系统,用于自定义名称和行为
    • C++26 P2996 反射 支持 — 非聚合体、自动枚举、无限成员
  • 符合 JSON RFC 8259 标准,并支持 UTF-8 验证
  • 支持标准 C++ 库
  • 仅头文件(独立的格式头文件避免编译开销)
  • 直接到内存的序列化/反序列化
  • 编译时映射,支持常数时间查找和完美哈希
  • 强大的包装器,用于修改读写行为(包装器)
  • 使用您自己的自定义读写函数(自定义读写)
  • 处理未知键 的方式快速且灵活
  • 无异常(使用 -fno-exceptions 编译)
    • 如果您希望使用抛出异常的辅助函数以获得更简洁的语法,请参阅 Glaze 异常
  • 无需运行时类型信息(使用 -fno-rtti 编译)
  • JSON Schema 生成
  • 部分读取部分写入 支持
  • 流式 I/O 用于以有界内存读取/写入大文件
  • 更多功能!

性能

往返时间 (s)写入 (MB/s)读取 (MB/s)
Glaze1.0113961200
simdjson (on demand)N/AN/A1163
yyjson1.2210231106
reflect_cpp3.15488365
daw_json_link3.29334479
RapidJSON3.76289416
json_struct5.87178316
Boost.JSON5.38198308
nlohmann15.448681

性能测试代码在此处

性能注意事项:simdjsonyyjson 非常优秀,但当数据不在预期序列中或任何键缺失时,它们会出现严重的性能损失(随着文件大小的增加,问题会加剧,因为它们必须重新遍历文档)。

此外,simdjsonyyjson 不支持自动处理转义字符串,因此如果此基准测试中当前未转义的字符串包含转义字符,这些转义字符将不会被处理。

ABC Test 展示了当键的顺序不符合预期时,simdjson 的性能表现不佳:

LibraryRead (MB/s)
Glaze1219
simdjson (on demand)89

Binary Performance

带标签的二进制规范:BEVE

MetricRoundtrip Time (s)Write (MB/s)Read (MB/s)
Raw performance0.4232352468
Equivalent JSON data*0.4235472706

JSON 大小:670 字节

BEVE 大小:611 字节

*BEVE 的打包效率比 JSON 更高,因此传输相同数据的速度更快。

Examples

[!TIP]

请参阅 example_json 单元测试,了解如何使用 Glaze 的基本示例。请参阅 json_test 了解功能的全面测试。

您的结构体将自动获得反射支持!用户无需提供元数据。

struct my_struct
{
  int i = 287;
  double d = 3.14;
  std::string hello = "Hello World";
  std::array<uint64_t, 3> arr = { 1, 2, 3 };
  std::map<std::string, int> 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<my_struct>(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{});

[!IMPORTANT]

文件名(第二个参数)必须以 null 结尾。

写入流

对于向 std::ostream 目标(文件、网络等)进行流式传输且内存受限的情况:

#include "glaze/core/ostream_buffer.hpp"

std::ofstream file("output.json");
glz::basic_ostream_buffer<std::ofstream> buffer(file);  // Concrete type for performance
auto ec = glz::write_json(obj, buffer);

缓冲区在序列化过程中增量刷新,从而以固定内存实现任意大小的输出。有关缓冲区类型和流概念的详细信息,请参阅 流式 I/O

从流中读取

对于来自 std::istream 源(文件、网络等)的流式传输,且内存受限:

#include "glaze/core/istream_buffer.hpp"

std::ifstream file("input.json");
glz::basic_istream_buffer<std::ifstream> buffer(file);
my_struct obj;
auto ec = glz::read_json(obj, buffer);

缓冲区在解析过程中自动重新填充,从而能够以固定内存读取任意大小的输入。请参阅 流式 I/O 以了解 NDJSON 处理及其他流式模式。

编译器/系统支持

  • 需要 C++23
  • 已针对 64 位和 32 位进行测试
  • 支持小端序和大端序系统

Actions 使用 Clang (18+)、MSVC (Visual Studio 2026 MSVC Build Tools 14.50) 和 GCC (13+) 在 apple、windows 和 linux 上进行构建和测试。大端序通过 s390x 上的 QEMU 模拟进行测试。

clang build gcc build msvc build

Glaze 力求与 GCC 和 Clang 的最新三个版本,以及 MSVC 和 Apple Clang (Xcode) 的最新版本保持兼容。并且,我们旨在仅在主要版本发布时弃用旧版本。

MSVC 编译器标志

Glaze 需要一个符合 C++ 标准的预处理器,这要求在使用 MSVC 构建时启用 /Zc:preprocessor 标志。

SIMD 架构检测

Glaze 会自动检测目标架构,并使用编译器预定义宏启用平台特定的 SIMD 优化,涵盖 x86-64 上的 SSE2 到 AVX-512BW、ARM 上的 NEON 以及 WebAssembly 上的 SIMD128。由于这些是由编译器设置的目标架构宏,交叉编译会自动正常工作(例如,x86 主机为 ARM 进行交叉编译时,不会启用 x86 SIMD 路径)。请参阅 优化性能 以获取完整表格。

要报告构建实际编译的内容,请读取 glz::simd_info

std::string report;
std::ignore = glz::write_json(glz::simd_info, report);
// {"detected":"AVX512BW","utf8_validation":"AVX512BW","string_escape":"AVX2","float_write":"SSE4.1"}

要禁用 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 反射 作为结构体反射的替代后端。这用标准化的反射原语取代了传统的 __PRETTY_FUNCTION__ 解析。

set(glaze_ENABLE_REFLECTION26 ON)

需要 GCC 16+Bloomberg clang-p2996,并带有以下标志:

GCC 16+:

-std=c++26 -freflection

Bloomberg clang-p2996:

-std=c++26 -freflection -fexpansion-statements -stdlib=libc++

其优势包括无限制的 struct 成员(传统反射为 128 个)、更简洁的类型名称,以及对未来 C++ 标准的合规性。详见 P2996 Reflection

优化级别(嵌入式/尺寸优化)

Glaze 提供优化级别,以控制二进制大小与运行时性能之间的权衡。这对于嵌入式系统非常有用:

auto json = glz::write<glz::opts_size{}>(obj);
auto ec = glz::read<glz::opts_size{}>(obj, buffer);
级别预设描述
normal(默认)最大性能(整数和浮点数的查找表约 278KB)
sizeopts_size最小二进制(整数表约 400B,浮点数 std::to_chars,线性搜索)

有关完整详情,请参阅 优化级别

如何使用 Glaze

FetchContent

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

find_package(glaze REQUIRED)

target_link_libraries(main PRIVATE glaze::glaze)

build2

import libs = libglaze%lib{glaze}

Arch Linux

参见此示例仓库了解如何在新项目中使用 Glaze


参见 FAQ 获取常见问题解答

显式元数据

如果你想要专门化你的反射,那么你可以可选地编写以下代码:

此元数据对于非聚合可初始化结构体也是必需的。

template <>
struct glz::meta<my_struct> {
   using T = my_struct;
   static constexpr auto value = object(
      &T::i,
      &T::d,
      &T::hello,
      &T::arr,
      &T::map
   );
};

本地 Glaze 元数据

Glaze 还支持其关联类中的元数据:
struct my_struct
{
  int i = 287;
  double d = 3.14;
  std::string hello = "Hello World";
  std::array<uint64_t, 3> arr = { 1, 2, 3 };
  std::map<std::string, int> 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<my_struct> {
   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
   );
};

这些字符串均为可选,如果希望名称得以体现,可以针对各个字段将其移除。

以下情况需要名称:

  • static constexpr 成员变量
  • Wrappers
  • Lambda 函数

使用 modify 扩展纯反射

如果你只需要调整少数几个字段,可以使用 glz::meta<T>::modify 将这些更改叠加在自动反射的成员之上:

struct server_status
{
   std::string name;
   std::string region;
   uint64_t active_sessions{};
   std::optional<std::string> maintenance;
   double cpu_percent{};
};

template <> struct glz::meta<server_status>
{
   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 中提供的额外键会被叠加在上方。

Reflection API

Glaze 提供了一个编译时反射 API,可通过 glz::meta 特化进行修改。该反射 API 使用纯反射,除非提供了 glz::meta 特化,在这种情况下,默认行为会被开发者覆盖。

static_assert(glz::reflect<my_struct>::size == 5); // Number of fields
static_assert(glz::reflect<my_struct>::keys[0] == "i"); // Access keys

[!WARNING]

上述描述的 glz::reflect 字段已经正式确定,不太可能再发生变化。随着我们继续完善规范,其他字段可能会发生变化。

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。然而,这仅适用于用户定义的类型。

对于常见用例,或需要为特定成员变量设置特殊读写逻辑的情况,可以使用 glz::custom 来注册读写成员函数、std::functions 或 lambda 函数。

参见示例:
struct custom_encoding
{
   uint64_t x{};
   std::string y{};
   std::array<uint32_t, 3> 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<custom_encoding>
{
   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<uint32_t, 3>{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<custom_buffer_input>
{
   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<read_x, write_x>);
};

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<age_custom_error_obj>
{
   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<read_x, &T::age>);
};

使用中:

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<Thing> {
   static constexpr auto value = object(
      "i", [](auto&& self) -> auto& { return self.subclass.i; }
   );
};

传递给 lambda 函数的值 self 将是一个 Thing 对象,并且 lambda 函数允许我们使该子类对对象接口不可见。

Lambda 函数默认复制返回值,因此通常需要 auto& 返回类型,以便 glaze 能够写入内存。

请注意,通过指针/引用也可以实现重映射,因为 glaze 在写入/读取时以相同方式处理值、指针和引用。

值类型

类可以按以下方式作为底层值处理:

struct S {
  int x{};
};

template <>
struct glz::meta<S> {
  static constexpr auto value{ &S::x };
};

或使用 lambda:

template <>
struct glz::meta<S> {
  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<constrained_object>
{
   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<T> 并在你的类中添加 friend struct glz::meta<T>; 来序列化和反序列化私有字段。

参见示例:
class private_fields_t
{
private:
   double cash = 22.0;
   std::string currency = "$";

   friend struct glz::meta<private_fields_t>;
};

template <>
struct glz::meta<private_fields_t>
{
   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 完全相同。

Glaze 致力于短路错误处理,这意味着如果遇到错误,解析会非常迅速地退出。

要生成更有帮助的错误消息,请调用 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 指示错误发生的位置

输入缓冲区 (Null) 终止

建议输入缓冲区使用非 const std::string,因为这允许 Glaze 通过临时填充来提高性能,并且缓冲区将以 null 结尾。

JSON

默认情况下,选项 null_terminated 被设置为 true,解析 JSON 时必须使用以 null 结尾的缓冲区。可以通过关闭该选项来允许使用非 null 结尾的缓冲区,但会略微降低性能:

constexpr glz::opts options{.null_terminated = false};
auto ec = glz::read<options>(value, buffer); // read in a non-null terminated buffer

BEVE

BEVE 不需要空终止符。这对性能没有影响。

CBOR

CBOR 不需要空终止符。这对性能没有影响。

CSV

CSV 不需要空终止符。这对性能没有影响。

类型支持

数组类型

数组类型在逻辑上转换为 JSON 数组值。使用概念(Concepts)来允许各种容器,甚至如果它们匹配标准库接口,也允许用户自定义容器。

  • glz::array(编译时混合类型)
  • std::tuple(编译时混合类型)
  • std::array
  • std::vector
  • std::deque
  • std::list
  • std::forward_list
  • std::span
  • std::set
  • std::unordered_set

对象类型

对象类型在逻辑上转换为 JSON 对象值,例如映射(maps)。与 JSON 一样,Glaze 将对象定义视为无序映射。因此,对象布局的顺序不必与 C++ 中的相同二进制序列匹配。

  • glz::object(编译时混合类型)
  • std::map
  • std::unordered_map
  • std::pair(在栈存储中启用动态键)

std::pair 被处理为具有单个键和值的对象,但当在数组中使用 std::pair 时,Glaze 会将这些键值对连接成一个单一的对象。std::vector<std::pair<...>> 将序列化为单个对象。如果你不想要这种行为,请设置编译时选项 .concatenate = false

变体

  • std::variant

有关更多信息,请参阅 Variant Handling

可空类型

  • std::unique_ptr
  • std::shared_ptr
  • std::optional

可空类型可由有效输入分配,或由 null 关键字置空。

std::unique_ptr<int> 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<Color> {
   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\"");

[!TIP]

若要实现无需为每个枚举编写元数据的自动枚举到字符串序列化,请使用枚举反射库(magic_enumenchantumsimple_enum),并配合通用的 glz::meta 特化。详情请参阅 Automatic Enum Strings

JSON With Comments (JSONC)

支持在此处定义的规范中使用的注释:JSONC

对注释的读取支持通过 glz::read_jsoncglz::read<glz::opts{.comments = true}>(...) 提供。

Prettify JSON

格式化后的 JSON 可以直接通过编译时选项写出:

auto ec = glz::write<glz::opts{.prettify = true}>(obj, buffer);

或者,JSON 文本可以使用 glz::prettify_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<glz::opts{.minified = true}>(obj, buffer);

布尔标志

Glaze 支持注册一组布尔标志,其行为类似于字符串选项数组:

struct flags_t {
   bool x{ true };
   bool y{};
   bool z{ true };
};

template <>
struct glz::meta<flags_t> {
   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" 会被写出,因为它们是 true。从缓冲区读取会设置相应的布尔值。

在写入 BEVE 时,flags 每个布尔值仅使用一个位(按字节对齐)。

记录 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]})");

对于通用 JSON,这种方法比 glz::generic 显著更快。但可能并不适用于所有场景。

Merge

glz::merge 允许用户将多个 JSON 对象类型合并为单个对象。

glz::obj o{"pi", 3.141};
std::map<std::string_view, int> 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}

glz::merge 存储 lvalue 的引用以避免拷贝

通用 JSON

参见 Generic JSON 以了解 glz::generic

glz::generic json{};
std::string buffer = R"([5,"Hello World",{"pi":3.14}])";
glz::read_json(json, buffer);
assert(json[2]["pi"].get<double>() == 3.14);

惰性 JSON

参见 Lazy JSON 用于 glz::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<int>(); // Only parses what you access
}

glz::lazy_json 提供按需解析功能,无需任何预处理,非常适合从大型 JSON 文档中提取少量字段。

glz::lazy_beve 为 BEVE 二进制格式提供相同的惰性解析能力。参见 Lazy BEVE

Raw Buffer Performance

Glaze 写入 std::string 的速度几乎与写入原始 char 缓冲区一样快。如果缓冲区中有足够分配的可用空间,你可以像下面所示那样写入原始缓冲区,但不建议这样做。

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<char, 1024> 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_json(...),你还可以调用 glz::read<glz::opts{}>(...) 并自定义选项。

例如:glz::read<glz::opts{.error_on_unknown_keys = false}>(...) 将关闭对未知键的错误处理,并简单跳过这些项。

glz::opts 还可以在不同格式之间切换:

  • glz::read<glz::opts{.format = glz::BEVE}>(...) -> glz::read_beve(...)
  • glz::read<glz::opts{.format = glz::JSON}>(...) -> glz::read_json(...)

[!IMPORTANT]

请参阅 Options 以获取所有编译时选项的综合参考表,包括可添加到自定义选项结构体中的可继承选项。

常用编译时选项

glz::opts 结构体提供默认选项。以下是最常用的选项:

选项默认值描述
formatJSON格式选择器(JSONBEVECSVTOML
null_terminatedtrue输入缓冲区是否以空字符结尾
error_on_unknown_keystrue遇到未知 JSON 键时是否报错
skip_null_memberstrue写入时是否跳过 null 值
prettifyfalse是否输出格式化 JSON
minifiedfalse是否要求输入为压缩格式(解析更快)
error_on_missing_keysfalse是否要求所有键都存在
partial_readfalse读取完最深层对象后是否退出

可继承选项(默认不在 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<opts>(obj, buffer);

参见 Options 获取带有详细描述的完整列表,以及 Wrappers 获取逐字段选项。

JSON 一致性

默认情况下,Glaze 严格符合最新的 JSON 标准,除了两个具有相关选项的情况:

  • validate_skipped 此选项在解析时对跳过的值执行完整的 JSON 验证。默认未设置此选项,因为当用户不关心某些值时,通常会跳过这些值,并且 Glaze 仍然会验证主要问题。但是,通过不关心跳过的值是否完全符合 JSON 规范,这使得跳过操作更快。例如,默认情况下,Glaze 会确保跳过的数字包含所有有效的数字字符,但除非开启 validate_skipped,否则它不会验证诸如跳过数字中的前导零等问题。在 Glaze 解析用于使用的值时,会进行完整验证。
  • validate_trailing_whitespace 此选项验证已解析文档中的尾部空白。由于 Glaze 解析 C++ 结构体,通常不需要在读取完感兴趣的对象后继续解析。如果您希望确保文档的其余部分具有有效的空白,请开启此选项,否则 Glaze 将在解析完感兴趣的内容后忽略后续内容。

读取时会验证 UTF-8 编码,因为 RFC 8259 第 8.1 节要求如此。validate_utf8 选项会为此关闭验证,适用于编码已得到保证的输入,代价是失去一致性。参见 UTF-8 Validation

[!NOTE]

默认情况下,Glaze 不会对控制字符进行 Unicode 转义(例如 "\x1f""\u001f"),因为这存在在字符串中嵌入空字符和其他不可见字符的风险。对于希望在字符串中将控制字符写为转义 Unicode 的用户,可以使用编译时选项 escape_control_characters

// Example options for enabling escape_control_characters
struct options : glz::opts {
   bool escape_control_characters = true;
};

Skip

在对象中确认某个键的存在以防止错误可能很有用,但该值在 C++ 中可能不需要或不存在。这些情况通过向元数据注册 glz::skip 类型来处理。

参见示例:
struct S {
  int i{};
};

template <>
struct glz::meta<S> {
  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

Hide

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<hide_struct> {
   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 数字直接解析为 doubleint 等类型。

struct A {
   double x;
   std::vector<uint32_t> y;
};

template <>
struct glz::meta<A> {
   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 数字将被直接解析为 doublestd::vector<uint32_t>glz::quoted 函数同样适用于嵌套的对象和数组。

JSON Lines (NDJSON) 支持

Glaze 支持 JSON Lines(或 Newline Delimited JSON)用于类数组类型(例如 std::vectorstd::tuple)。

std::vector<std::string> x = { "Hello", "World", "Ice", "Cream" };
std::string s = glz::write_ndjson(x).value_or("error");
auto ec = glz::read_ndjson(x, s);

更多功能

数据记录器

命令行界面菜单

JMESPath

  • 查询 JSON

JSON 包含系统

JSON Pointer 语法

JSON-RPC 2.0

JSON Schema

共享库 API

流式 I/O

带标签的二进制消息

线程池

时间跟踪性能分析

  • 将性能配置文件输出为 JSON 并使用 Perfetto 进行可视化

封装器

扩展

请参阅 ext 目录以获取扩展。

许可证

Glaze 根据 MIT 许可证分发,但嵌入式形式除外:

--- 许可证的可选例外 ---

作为例外,如果由于您编译源代码的结果,本软件的部分内容被嵌入到该源代码的机器可执行对象形式中,您可以重新分发此类嵌入式部分,而无需包含版权声明和许可声明。