世界上最快的 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 許可證分發,但嵌入式表單除外:
--- 許可證的可選例外 ---
作為例外,如果由於您編譯原始程式碼而導致本軟體的部分內容嵌入到此類原始程式碼的機器可執行物件形式中,您可以以此類物件形式重新散佈此類嵌入部分,而無需包含版權和許可通知。