世界上最快的 JSON 库之一。 Glaze 从对象内存中读取和写入,简化了接口并提供了令人难以置信的性能。
Glaze 还支持:
结构体的纯编译时反射
JSON RFC 8259 符合 UTF-8 验证
标准 C++ 库支持
仅标题
直接内存序列化/反序列化
通过恒定时间查找和完美哈希来编译时间图
用于修改读/写行为的强大包装器(Wrappers)
使用您自己的自定义读/写函数(Custom Read/Write)
快速灵活地处理未知密钥
通过 JSON 指针语法直接访问内存
通过相同 API 的二进制数据以获得最佳性能
无例外(使用-fno-exceptions
编译)
不需要运行时类型信息(使用-fno-rtti
编译)
通过短路进行快速错误处理
JSON-RPC 2.0 支持
JSON 模式生成
极其便携,使用精心优化的 SWAR(寄存器内的 SIMD)来实现广泛的兼容性
部分读取和部分写入支持
CSV 读/写
还有更多!
有关更多文档,请参阅 DOCS。
图书馆 | 往返时间(秒) | 写入(MB/秒) | 读取(MB/秒) |
---|---|---|---|
釉 | 1.04 | 1366 | 1224 |
simdjson(按需) | 不适用 | 不适用 | 1198 |
yyjson | 1.23 | 1005 | 1107 |
daw_json_link | 2.93 | 365 | 第553章 |
快速JSON | 3.65 | 290 | 450 |
Boost.JSON(直接) | 4.76 | 199 | 第447章 |
json_struct | 5.50 | 182 | 第326章 |
恩洛曼 | 15.71 | 84 | 80 |
性能测试代码可在此处获取
性能警告:simdjson 和 yyjson 很棒,但是当数据不符合预期顺序或丢失任何键时,它们会经历重大性能损失(随着文件大小的增加,问题会加剧,因为它们必须重新遍历文档)。
此外,simdjson 和 yyjson 不支持自动转义字符串处理,因此如果此基准测试中当前任何非转义字符串包含转义字符,则不会处理转义字符。
ABC 测试显示当键不符合预期顺序时 simdjson 的性能如何不佳:
图书馆 | 读取(MB/秒) |
---|---|
釉 | 第678章 |
simdjson(按需) | 93 |
标记的二进制规范:BEVE
公制 | 往返时间(秒) | 写入(MB/秒) | 读取(MB/秒) |
---|---|---|---|
原始性能 | 0.42 | 3235 | 2468 |
等效 JSON 数据* | 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< 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{});
重要的
文件名(第二个参数)必须以 null 结尾。
Actions 在 Apple、Windows 和 Linux 上使用 Clang (17+)、MSVC (2022) 和 GCC (12+) 进行构建和测试。
Glaze 力求保持与最新三个版本的 GCC 和 Clang 以及最新版本的 MSVC 和 Apple Clang 的兼容性。
Glaze 需要符合 C++ 标准的预处理器,在使用 MSVC 构建时需要/Zc:preprocessor
标志。
CMake 有选项glaze_ENABLE_AVX2
。在某些情况下,这将尝试使用AVX2
SIMD 指令来提高性能,只要您配置的系统支持它。将此选项设置为OFF
可禁用 AVX2 指令集,例如,如果您正在为 Arm 进行交叉编译。如果您不使用 CMake,则宏GLZ_USE_AVX2
会启用该功能(如果已定义)。
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)
find_package(glaze REQUIRED)
target_link_libraries(main PRIVATE glaze::glaze)
import libs = libglaze%lib{glaze}
如果您想专门化您的反射,那么您可以选择编写以下代码:
此元数据对于非聚合可初始化结构也是必需的。
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
);
};
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
);
};
这些字符串中的每一个都是可选的,如果您希望反映名称,则可以针对各个字段删除这些字符串。
需要姓名:
- 静态 constexpr 成员变量
- 包装纸
- Lambda 函数
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
警告
上面描述的glz::reflect
字段已经正式化并且不太可能改变。随着我们继续规范规范, glz::reflect
结构中的其他字段可能会发展。因此,未来未记录的字段可能会发生重大更改。
自定义读取和写入可以通过强大的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]} )" );
};
};
当使用成员指针(例如&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&
返回类型才能让 Glace 写入内存。
请注意,重新映射也可以通过指针/引用来实现,因为 Glace 在写入/读取时以相同的方式处理值、指针和引用。
类可以被视为基础值,如下所示:
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::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 无效。
建议对输入缓冲区使用非常量std::string
,因为这允许 Glaze 通过临时填充来提高性能,并且缓冲区将以 null 终止。
默认情况下,选项null_terminated
设置为true
,并且在解析 JSON 时必须使用 null 终止缓冲区。可以关闭该选项,但性能会略有损失,从而允许非空终止缓冲区:
constexpr glz::opts options{. null_terminated = false };
auto ec = glz::read<options>(value, buffer); // read in a non-null terminated buffer
解析 BEVE(二进制)时不需要空终止。它对性能没有影响。
警告
目前, null_terminated = false
对于 CSV 解析无效,并且缓冲区必须以 null 终止。
数组类型在逻辑上转换为 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::pair
被处理为具有单个键和值的对象,但是当在数组中使用std::pair
时,Glaze 将这些对连接成单个对象。std::vector<std::pair<...>>
将序列化为单个对象。如果您不希望出现此行为,请设置编译时选项.concatenate = false
。
std::variant
有关详细信息,请参阅变体处理。
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 " " );
此处定义的规范支持注释:JSONC
glz::read_jsonc
或glz::read<glz::opts{.comments = true}>(...)
提供对注释的读取支持。
格式化的 JSON 可以通过编译时选项直接写出:
auto ec = glz::write<glz::opts{. prettify = true }>(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:
auto ec = glz::write_json(obj, buffer); // default is minified
要缩小 JSON 文本调用:
std::string minified = glz::minify_json(buffer);
如果您希望需要缩小的 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"
,因为它们是真的。读取缓冲区将设置适当的布尔值。
写入 BEVE 时,
flags
每个布尔值仅使用一位(字节对齐)。
有时您只想尽可能高效地即时写出 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::json_t
快得多。但是,可能并不适合所有情况。
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
存储对左值的引用以避免复制
请参阅glz::json_t
的通用 JSON。
glz:: json_t json{};
std::string buffer = R"( [5,"Hello World",{"pi":3.14}] )" ;
glz::read_json (json, buffer);
assert (json[ 2 ][ " pi " ].get< double >() == 3.14);
Glaze 写入std::string
速度与写入原始字符缓冲区的速度一样快。如果缓冲区中有足够的分配空间,则可以写入原始缓冲区,如下所示,但不建议这样做。
glz::read_json(obj, buffer);
const auto n = glz::write_json(obj, buffer.data()).value_or(0);
buffer.resize(n);
glz::opts
结构定义了用于读/写的编译时可选设置。
您可以调用glz::read<glz::opts{}>(...)
并自定义选项,而不是调用glz::read_json(...)
。
例如: 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(...)
下面的结构显示了可用选项和默认行为。
struct opts {
uint32_t format = json;
bool comments = false ; // Support reading in JSONC style comments
bool error_on_unknown_keys = true ; // Error when an unknown key is encountered
bool skip_null_members = true ; // Skip writing out params in an object if the value is null
bool use_hash_comparison = true ; // Will replace some string equality checks with hash checks
bool prettify = false ; // Write out prettified JSON
bool minified = false ; // Require minified input for JSON, which results in faster read performance
char indentation_char = ' ' ; // Prettified JSON indentation char
uint8_t indentation_width = 3 ; // Prettified JSON indentation size
bool new_lines_in_arrays = true ; // Whether prettified arrays should have new lines for each element
bool shrink_to_fit = false ; // Shrinks dynamic containers to new size to save memory
bool write_type_info = true ; // Write type info for meta objects in variants
bool error_on_missing_keys = false ; // Require all non nullable keys to be present in the object. Use
// skip_null_members = false to require nullable members
bool error_on_const_read =
false ; // Error if attempt is made to read into a const value, by default the value is skipped without error
bool validate_skipped = false ; // If full validation should be performed on skipped values
bool validate_trailing_whitespace =
false ; // If, after parsing a value, we want to validate the trailing whitespace
uint8_t layout = rowwise; // CSV row wise output/input
// The maximum precision type used for writing floats, higher precision floats will be cast down to this precision
float_precision float_max_write_precision{};
bool bools_as_numbers = false ; // Read and write booleans with 1's and 0's
bool quoted_num = false ; // treat numbers as quoted or array-like types as having quoted numbers
bool number = false ; // read numbers as strings and write these string as numbers
bool raw = false ; // write out string like values without quotes
bool raw_string =
false ; // do not decode/encode escaped characters for strings (improves read/write performance)
bool structs_as_arrays = false ; // Handle structs (reading/writing) without keys, which applies
bool allow_conversions = true ; // Whether conversions between convertible types are
// allowed in binary, e.g. double -> float
bool partial_read =
false ; // Reads into only existing fields and elements and then exits without parsing the rest of the input
// glaze_object_t concepts
bool partial_read_nested = false ; // Advance the partially read struct to the end of the struct
bool concatenate = true ; // Concatenates ranges of std::pair into single objects when writing
bool hide_non_invocable =
true ; // Hides non-invocable members from the cli_menu (may be applied elsewhere in the future)
};
许多编译时选项都有包装器,可以将选项仅应用于单个字段。有关更多详细信息,请参阅包装器。
默认情况下,Glaze 严格符合最新的 JSON 标准,但有两种相关选项的情况除外:
validate_skipped
此选项在解析时对跳过的值进行完整的 JSON 验证。默认情况下不会设置此值,因为当用户不关心这些值时,通常会跳过这些值,并且 Glaze 仍会验证主要问题。但是,这使得跳过速度更快,因为它不关心跳过的值是否完全符合 JSON。例如,默认情况下,Glaze 将确保跳过的数字具有所有有效的数字字符,但除非启用validate_skipped
,否则不会验证跳过的数字中的前导零等问题。无论 Glaze 解析要使用的值,它都会经过充分验证。validate_trailing_whitespace
此选项验证已解析文档中的尾随空格。由于 Glaze 解析 C++ 结构,因此在读取感兴趣的对象后通常不需要继续解析。如果您想确保文档的其余部分具有有效的空白,请打开此选项,否则 Glaze 在解析感兴趣的内容后将忽略该内容。 笔记
Glaze 不会自动对控制字符进行 unicode 转义(例如"x1f"
到"u001f"
),因为这会带来在字符串中嵌入空字符和其他不可见字符的风险。将添加一个编译时选项来启用这些转换(开放问题:unicode escaped write),但这不会是默认行为。
确认对象中存在键以防止错误可能很有用,但 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
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 数字直接解析为double
、 int
等类型。
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 数字将直接解析为double
和std::vector<uint32_t>
。 glz::quoted
函数也适用于嵌套对象和数组。
Glaze 支持类似数组类型的 JSON 行(或换行符分隔 JSON)(例如std::vector
和std::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);
请参阅ext
目录以获取扩展。
Glaze 根据 MIT 许可证分发,但嵌入式表单除外:
--- 许可证的可选例外 ---
作为例外,如果由于您编译源代码而导致本软件的部分内容嵌入到此类源代码的机器可执行对象形式中,您可以以此类对象形式重新分发此类嵌入部分,而无需包含版权和许可通知。