JSON Link 库是一个高性能、无分配的 C++ JSON 库,支持:
该库还支持其他可以混合的解析模式。
json_array_iterator
或json_array_range
类型。json_value
类型其他一些值得注意的功能是:
boost::multiprecision::cpp_int
或 GNU BigNum/Rational mpq_t
该库正在使用 BSL 许可证
当 JSON 文档的结构已知时,解析如下:
MyThing thing = daw::json::from_json<MyThing>( json_string );
或者对于数组文档,文档的根是一个数组,有一个帮助器方法可以使它更容易,并且可以像下面这样解析:
std::vector<MyThing> things = daw::json::from_json_array<MyThing>( json_string2 );
如果 JSON 文档的结构未知,可以构造一个json_value
作为视图并允许按需迭代和拉取解析。以下是从 JSON 数据打开json_value
的示例:
json_value val = daw::json::json_value( json_string );
from_json
和to_json
方法允许访问大多数解析和序列化需求。
基于事件的解析器(SAX)可以通过daw::json::json_event_parser
调用。它需要两个参数,一个 json 文档和一个事件处理程序。事件处理程序可以通过具有以下任何成员来选择事件:
将类映射到 JSON 文档是通过专门化特征daw::json::json_data_contract
来完成的。如果已映射的类是另一个映射类的成员,则无需再次映射。特征json_data_contract
有两个部分,第一个是名为type
的类型别名,它将 JSON 成员映射到我们类的构造函数。这可以避免需要对类进行私有访问,假设构造类也需要我们序列化的数据。例如:
struct Thing {
int a;
int b;
};
Thing
的构造需要 2 个整数,如果我们有以下 JSON:
{
"a" : 42 ,
"b" : 1234
}
我们可以像下面这样进行映射:
namespace daw ::json {
template <>
struct json_data_contract <Thing> {
static constexpr char const a[] = " a " ;
static constexpr char const b[] = " b " ;
using type = json_member_list<
json_number<a, int >,
json_number<b, int >
>;
};
}
这表示文档中的 JSON 类将至少有两个成员“a”和“b”,它们都是整数。当daw::json::from_json<Thing>( json_doc );
时,它们将被传递给Thing
的构造函数。被调用,或者另一个类具有json_class<MemberName, Thing>
成员映射。以上是 C++17 的名称映射方法,它也适用于未来的 C++ 版本。但是,在 C++20 及更高版本中,名称可以内联在映射中,例如json_number<"a", int>
。以上就是解析 JSON 所需的全部内容,序列化特征中需要一个静态成员函数。采用前面的示例并对其进行扩展,我们可以使用以下方式序列化Thing
:
namespace daw ::json {
template <>
struct json_data_contract <Thing> {
static constexpr char const a[] = " a " ;
static constexpr char const b[] = " b " ;
using type = json_member_list<
json_number<a, int >,
json_number<b, int >
>;
};
static auto to_json_data ( Thing const & v ) {
return std::forward_as_tuple ( v. a , v. b );
}
}
作为元组返回的成员的顺序需要与类型别名type
中的映射匹配。如果数据成员不是公共的,这也允许传递访问器方法的结果。此外,类Thing
必须可以从int, int
构造。该库支持 C++17 中的常规构造函数和聚合 init( Thing{ int, int }
和Thing( int, int )
)。
to_json_data
的返回类型不必返回对现有对象成员的引用元组,但也可以返回计算值。它不允许右值通过,因为它们通常是临时的,并且可能导致长距离调试。该库将对此进行 static_assert 并建议包含<daw/daw_tuple_forward.h>
并调用daw::forward_nonrvalue_as_tuple( ... )
来存储临时数据并转发其他引用类型。解析器通过在调用类的构造函数时就地构造每个参数来工作。可以针对数据的指定情况(例如浮点和整数)调整各个参数解析器。然后,通过定义构造 C++ 类所需的参数及其顺序的类型特征,我们可以查看 JSON 中的每个成员。现在我们用每个解析器的结果构造该值;类似于T{ parse<0, json_string<"name">>( data ), parse<1, json_number<"age", unsigned>>( data ), parse<json_number<2, "number>>( data )}
对于每个成员,数据流将向前移动,直到找到需要解析的成员,存储感兴趣的位置以供以后解析。此过程允许我们也通过json_class<"member_name", Type>
将其他类解析为成员。映射类型。这样每个映射特征只需处理其特定成员,而不需要处理它们的详细信息。
在未命名的上下文中,例如根值、数组元素、某些键值类型和名称为no_name
的变体元素列表,可以使用一些本机 C++ 数据类型而不是 JSON 映射类型。这包括整数、浮点、布尔、std::string、std::string_view、关联容器、序列容器、Nullable/Optional 等类型和先前映射的类。
例如,映射字符串数组。
template <>
struct daw ::json::json_data_contract<MyType> {
using type = json_member_list<json_array< " member_name " , std::string>>;
};
可以使用 vcpkg 获取最新版本,端口名为daw-json-link
find_package ( daw-json-link )
#...
target_link_libraries ( MyTarget daw::daw-json-link )
该库只是头文件,可以克隆,以及它的两个依赖项,然后将每个依赖项的include/
子文件夹添加到编译器的包含路径中
要在 cmake 项目中使用 daw_json_link,添加以下内容应允许将其与依赖项一起拉入:
include ( FetchContent )
FetchContent_Declare(
daw_json_link
GIT_REPOSITORY https://github.com/beached/daw_json_link
GIT_TAG release
)
FetchContent_MakeAvailable(daw_json_link)
#...
target_link_libraries ( MyTarget daw::daw-json-link )
在有bash的系统上,在其他系统上也类似,以下可以为系统安装
git clone https://github.com/beached/daw_json_link
cd daw_json_link
mkdir build
cd build
cmake ..
cmake --install .
这将允许 cmake find_package 安装或将其用作常规标头,只要安装前缀的包含文件夹包含在编译器的包含路径中即可
以下将构建并运行测试。
git clone https://github.com/beached/daw_json_link
cd daw_json_link
mkdir build
cd build
cmake -DDAW_ENABLE_TESTING=On ..
cmake --build .
ctest .
构建完成后,也可以测试各个示例。 city_test_bin
需要城市 JSON 文件的路径。
./tests/city_test_bin ../test_data/cities.json
如果可能的话,数据结构中成员的顺序通常应与 JSON 数据的顺序匹配。如果解析器不必回溯值,则它会更快。当 JSON 数据中缺少可选值时,也会减慢解析速度。如果可能的话,将它们作为空发送。解析器不分配。解析为数据类型可能会允许人们使用自定义分配器或混合分配器,因为它们的数据结构将进行分配。数组的默认值是使用 std::vector,如果不需要,则必须提供类型。
目前,该库在序列化时不会对成员名称进行转义/转义,它们应该是有效且未转义的。这可能是未来的可选添加,因为它确实有成本。
C++17 和 C++20 之间存在细微差别,其中 C++20 允许使用 C++17 中不可用的某些代码。
namespace daw ::json {
template <>
struct json_data_contract <MyType> {
static constexpr char const member_name[] = " memberName " ;
using type = json_member_list<json_number<member_name>>;
};
}
C++ 的两个版本都支持这种命名成员的方法。
在 C++20 编译器中编译时,除了像 C++17 中那样传递char const *
之外,还可以将成员名称直接指定为字符串文字。 C++20 编译器支持还很早期,这里是龙。 C++20 模式下的 g++9.x 存在已知问题,并且仅使用 g++10/11 进行了测试。这里有龙
namespace daw ::json {
template <>
struct json_data_contract <MyType> {
using type = json_member_list<json_number< " member_name " >>;
};
}
一旦数据类型被映射到json_data_contract
,该库就会提供解析 JSON 的方法。
MyClass my_class = from_json<MyClass>( json_str );
或者,如果输入是可信的,则检查较少的版本可能会更快
MyClass my_class = from_json<MyClass, options::parse_flags<options::CheckedParseMode::no>>( json_str );
具有数组根的 JSON 文档使用from_json_array
函数进行解析
std::vector<MyClass> my_data = from_json_array<MyClass>( json_str );
或者,如果输入是可信的,则检查较少的版本可能会更快
std::vector<MyClass> my_data = from_json_array<MyClass, std::vector<MyClass>, options::parse_flags<options::CheckedParseMode::no>>( json_str );
json_array_iterator
如果你想使用 JSON 数组数据,你可以获取一个迭代器并使用 std 算法来迭代 JSON 数据中的数组可以通过json_array_iterator
完成
using iterator_t = json_array_iterator<MyClass>;
auto pos = std::find( iterator_t ( json_str ), iterator_t ( ), MyClass( ... ) );
或者,如果输入可信,您可以调用较少检查的版本
using iterator_t = daw::json::json_array_iterator<MyClass, options::CheckedParseMode::no>;
auto pos = std::find( iterator_t ( json_str ), iterator_t ( ), MyClass( ... ) );
json_value
对于像 api 这样的 DOM,通常用于 GUI 之类的东西并在映射不充分时提供代码,可以使用json_value
。这在 json_to_cpp 工具中使用。
auto jv = daw::json::json_value( json_doc );
可以使用 JSON Path 来提取整数
int foo = as< int >( jv[ " path.to.int " ] );
这里, "path.to.int"
是一个 JSON 路径,表示钻取 JSON 类,例如
{
"path" : {
"to" : {
"int" : 5
}
}
}
人们也可以通过 JSON 路径中的类似数组的语法进行选择, "path[5]"
将选择"path"
的第 5 个元素/成员。如果你想序列化为 JSON。 JSON 路径语法也适用于from_json
、 from_json_array
和json_array_iterator
。
to_json
std::string my_json_data = to_json( MyClass{} );
或者序列化事物的数组、集合、范围或视图。仅需要std::begin(...)
和std::end(...)
即可用于该类型。当类型不是可构造的事物集合时,这允许序列化。
std::vector<MyClass> arry = ...;
std::string my_json_data = to_json_array( arry );
解析错误默认抛出daw::json::json_exception
,其中包含有关失败原因和位置的信息。
如果禁用异常,则默认情况下库将在出现解析错误时调用std::terminate
。
同时,错误处理默认在错误时抛出daw::json::json_exception
,或者在禁用异常时调用std::terminate
。可以通过设置函数指针daw::json::daw_json_error_handler
来更改此行为。唯一的要求是该函数不返回。使用此功能的示例位于 error_handling_bench_test.cpp 中
错误检查可以在每个解析的基础上进行修改。 from_json
、 from_json_array
、 json_value
、 json_array_iterator
等都支持解析选项。可以为调用提供解析器选项。可用选项记录在 parser_policies 说明书项中。
daw::json::json_exception
有一个成员函数std::string_view reason( ) const
类似于std::exception
的what( )
,但返回一个比what( )
具有更多上下文的std::string
。如果要在具有异常的环境中禁用异常,可以定义DAW_JSON_DONT_USE_EXCEPTIONS
来禁用库抛出异常或设置处理程序,不再建议这样做,因为处理程序可以设置为两个默认值之一daw::json::default_error_handling_throwing
或daw::json::default_error_handling_terminating
。
这可以通过在daw::json
命名空间中编写json_data_contract
的特化来完成。例如:
# include < daw/json/daw_json_link.h >
# include < string >
# include < string_view >
# include < vector >
struct TestClass {
int i = 0 ;
double d = 0.0 ;
bool b = false ;
std::string s{};
std::vector< int > y{};
TestClass ( int Int, double Double, bool Bool, std::string S,
std::vector< int > Y)
: i(Int), d(Double), b(Bool), s(std::move( S ) ), y(std::move( Y )) {}
};
namespace daw ::json {
template <>
struct json_data_contract <TestClass> {
using type =
json_member_list<
json_number< " i " , int >,
json_number< " d " >,
json_bool< " b " >,
json_string< " s " >,
json_array< " y " , int >
>;
};
} // namespace daw::json
int main () {
std::string_view test_001_t_json_data = R"( {
"i":5,
"d":2.2e4,
"b":false,
"s":"hello world",
"y":[1,2,3,4]
} )" ;
std::string_view json_array_data = R"( [{
"i":5,
"d":2.2e4,
"b":false,
"s":"hello world",
"y":[1,2,3,4]
},{
"i":4,
"d":122e4,
"b":true,
"s":"goodbye world",
"y":[4,3,1,4]
}] )" ;
TestClass test_class = daw::json::from_json<TestClass>(test_001_t_json_data);
std::vector<TestClass> arry_of_test_class =
daw::json::from_json_array<TestClass>(test_001_t_json_data);
}
请参阅编译器资源管理器
支持聚合构造函数和用户构造函数。该描述提供了构造类型和顺序所需的值。指定的顺序是它们放入构造函数中的顺序。还有一些自定义点可以提供一种构建类型的方法。像这样的类:
# include < daw/json/daw_json_link.h >
struct AggClass {
int a{};
double b{};
};
namespace daw ::json {
template <>
struct json_data_contract <AggClass> {
using type = json_member_list<
json_number< " a " , int >,
json_number< " b " >
>;
};
}
也有效。相同,但 C++17
# include < daw/json/daw_json_link.h >
struct AggClass {
int a{};
double b{};
};
namespace daw ::json {
template <>
struct json_data_contract <AggClass> {
static inline constexpr char const a[] = " a " ;
static inline constexpr char const b[] = " b " ;
using type = json_member_list<
json_number<a, int >,
json_number<b>
>;
};
}
类描述与其子成员是递归的。使用前一个AggClass
可以将其作为另一个类的成员
// See above for AggClass
struct MyClass {
AggClass other;
std::string_view some_name;
};
namespace daw ::json {
template <>
struct json_data_contract <MyClass> {
using type = json_member_list<
json_class< " other " , AggClass>,
json_string< " id " , std::string_view>
>;
};
}
上面映射了一个类MyClass
,该类具有另一个被描述为 AggClass 的类。此外,您还可以看到 C++ 类的成员名称不必与映射的 JSON 名称匹配,并且字符串可以使用std::string_view
作为结果类型。如果您可以保证包含 JSON 文件的缓冲区与类一样存在,那么这将是一项重要的性能增强。
迭代 JSON 数组。输入迭代器daw::json::json_array_iterator<JsonElement>
允许对 JSON 元素数组进行迭代。从技术上讲,它是一个输入迭代器,但可以像前向迭代器一样存储和重用。它不返回引用,而是返回值。
# include < daw/json/daw_json_link.h >
# include < daw/json/daw_json_iterator.h >
# include < iostream >
struct AggClass {
int a{};
double b{};
};
namespace daw ::json {
template <>
struct json_data_contract <AggClass> {
using type = json_member_list<
json_number< " a " , int >,
json_number< " b " >
>;
};
} // namespace daw::json
int main () {
std::string json_array_data = R"( [
{"a":5,"b":2.2},
{"a":5,"b":3.14},
{"a":5,"b":0.122e44},
{"a":5334,"b":34342.2}
] )" ;
using iterator_t = daw::json::json_array_iterator<AggClass>;
auto pos =
std::find_if (
iterator_t (json_array_data),
iterator_t (),
[](AggClass const &element) {
return element. b > 1000.0 ;
}
);
if (pos == iterator_t ()) {
std::cout << " Not found n " ;
} else {
std::cout << " Found n " ;
}
}
解析可以从特定成员或元素开始。可以指定from_json
、 from_json_array
、 json_value
、 json_array_iterator
等的可选成员路径。格式是点分隔的成员名称列表和可选的数组索引,例如member0.member1
,就像解析:
{
"member0" : {
"member1" : {}
}
}
或member0[5].member1
将从文档中的“member1”开始解析,例如:
{
"member0" : [
" a " ,
" b " ,
" c " ,
" d " ,
" e " ,
{
"member1" : " "
}
]
}
或者
{
"member0" : {
"a" : " " ,
"b" : " " ,
"c" : " " ,
"d" : " " ,
"e" : " " ,
"f" : {
"member1" : " "
}
}
}
当使用注释的解析器策略时支持注释。目前,评论政策有两种形式。
//
行注释和 C 风格/* */
注释。 { // This is a comment
"a" /*this is also a comment*/: "a's value"
}
#
行注释 { # This is a comment
"a" #this is also a comment
: "a's value"
}
评论策略可以通过PolicyCommentTypes
设置。有关详细信息,请参阅 parser_policies。
要启用序列化,必须在json_data_contract
的专门化中创建一个名为to_json_data( Thing const & );
返回一个成员元组。它将提供从您的类型到类描述中提供的参数的映射。要序列化为 JSON 字符串,请调用to_json( my_thing );
其中my_thing
是注册类型或基本类型之一,例如容器、映射、字符串、布尔值和数字。 to_json_data( Thing const & )
静态方法的结果是一个tuple
,其元素与随附的json_data_contract
类型别名type
中的顺序匹配。由于该方法的使用方式,具有右值元素的元组将导致销毁后使用错误。如果发生这种情况,编译器会出错。包含<daw/daw_tuple_forward.h>
和方法daw::forward_nonrvalue_as_tuple
将存储右值而不是通过引用传递它们。通常它是计算元组元素的结果。使用上面的示例让我们添加一个to_json_data
方法
# include < daw/json/daw_json_link.h >
# include < tuple >
struct AggClass {
int a;
double b;
};
namespace daw ::json {
template <>
struct json_data_contract <AggClass> {
using type = json_member_list<
json_number< " a " , int >,
json_number< " b " >
>;
static constexpr auto to_json_data ( AggClass const & value ) {
return std::forward_as_tuple ( value. a , value. b );
}
};
}
// ...
AggData value = // ...;
std::string test_001_t_json_data = to_json( value );
// or
std::vector<AggData> values = // ...;
std::string json_array_data = to_json_array( values );
或者,可以输出到任何 WritableOutput 类型,默认情况下包括 FILE*、iostream、字符容器和字符指针。在您类型的json_data_constract
中。或者,如果选择加入,则可以为其类型获取一个 ostream 运算符<<,通过添加名为opt_into_iostreams
类型别名(它的别名类型无关紧要)将 json 插入到输出流中,并包含daw/json/daw_json_iostream.h
。例如
# include < daw/json/daw_json_link.h >
# include < daw/json/daw_json_iostream.h >
# include < tuple >
struct AggClass {
int a{};
double b{};
};
namespace daw ::json {
template <>
struct json_data_contract <AggClass> {
using opt_into_iostreams = void ;
using type = json_member_list<
json_number< " a " , int >,
json_number< " b " >
>;
static inline auto to_json_data ( AggClass const & value ) {
return std::forward_as_tuple ( value. a , value. b );
}
};
}
// ...
AggData value = // ...;
std::cout << value << ' n ' ;
// or
std::vector<AggData> values = // ...;
std::cout << values << ' n ' ;
可以在 daw_json_iostream_test.cpp 或编译器资源管理器中找到工作示例
error: pointer to subobject of string literal is not allowed in a template argument
constexpr char const member_name[] = " member_name " ;
// ...
json_link<member_name, Type>
有一些定义会影响 JSON Link 的运行方式
DAW_JSON_DONT_USE_EXCEPTIONS
- 控制是否允许异常。如果不是,则会发生std::terminate()
错误。如果禁用异常(例如-fno-exceptions
),这是自动的DAW_ALLOW_SSE42
- 允许实验性 SSE42 模式,通常 constexpr 模式更快DAW_JSON_NO_CONST_EXPR
- 这可用于允许从 C++ 20 之前的 JSON 数据构造没有移动/复制特殊成员的类。当不再需要此标志时,此模式在 C++20 之前的常量表达式中不起作用。 较旧的编译器可能仍然可以工作,但在测试中,由于 C++17 支持存在缺陷,某些编译器会导致 ICE 或编译错误。通常不使用 constexpr 也有帮助。
json_key_value
解析类型可以保证两者都可以被解析或排序。std::multimap<std::string, T>
或std::vector<std::pair<std::string, T>>
一起使用时,所有成员均按前者的顺序保留。或者, json_value
类型将允许对类成员进行迭代并延迟解析正确的成员。请参阅演示这些方法的 Cookbook Key Values。