JSON
指针和JSON
补丁该库支持JSON
指针(RFC6901)
来处理结构化值
.而,JSONPatch(RFC6902)
允许描述两个JSON
值之间的差异
,有效地允许Unix
已知的补丁和差异
操作.
//一个`JSON`值
json j_original = R"({
"baz": ["one", "two", "three"],
"foo": "bar"
})"_json;
//使用`JSON`指针访问成员`(RFC6901)`
j_original["/baz/1"_json_pointer];
//"两个"一个`JSON`修补程序`(RFC6902)`
json j_patch = R"([
{ "op": "replace", "path": "/baz", "value": "boo" },
{ "op": "add", "path": "/hello", "value": ["world"] },
{ "op": "remove", "path": "/foo"}
])"_json;
//应用修补程序
json j_result = j_original.patch(j_patch);
//`{"baz":"boo","hello":["world"]}`从两个`JSON`值计算一个`JSON`补丁
json::diff(j_result, j_original);
//`[{"op":"replace","path":"/baz","value":["one","two","three"]},{"op":"remove","path":"/hello"},{"op":"add","path":"/foo","value":"bar"}]`
JSON
合并补丁使用与正在修改
文档类似语法
来描述更改.
//一个`JSON`值
json j_document = R"({
"a": "b",
"c": {
"d": "e",
"f": "g"
}
})"_json;
//一个补丁
json j_patch = R"({
"a":"z",
"c": {
"f": null
}
})"_json;
//应用修补程序
j_document.merge_patch(j_patch);
//`{"a":"z","c":{"d":"e"}}`
可隐式转换支持类型
为JSON
值.
建议不要使用JSON
值的隐式转换.不推荐原因,你可通过在包含json.hpp
头文件之前定义JSON_USE_IMPLICIT_CONVERSIONS
为0来关闭隐式转换
.
使用CMake
时,还可通过设置选项JSON_ImplicitConversions
为OFF
来实现此目的.
//串
std::string s1 = "Hello, world!";
json js = s1;
auto s2 = js.template get<std::string>();
//不推荐
std::string s3 = js;
std::string s4;
s4 = js;
//布尔值
bool b1 = true;
json jb = b1;
auto b2 = jb.template get<bool>();
//不推荐
bool b3 = jb;
bool b4;
b4 = jb;
//数字
int i = 42;
json jn = i;
auto f = jn.template get<double>();
//不推荐
double f2 = jb;
double f3;
f3 = jb;
//等.
注意,char
类型不会自动转换
为JSON
串,而是转换为整数
.必须显式指定
转换串:
char ch = 'A'; //`ASCII`值`65`存储整数`65`存储串`"A"`
json j_default = ch; //
json j_string = std::string(1, ch); //
每个类型都可在JSON
中序化,而不仅是STL
容器和标量类型.一般,你会按这些思路干活:
namespace ns {
//对人建模的简单结构
struct person {
std::string name;
std::string address;
int age;
};
}
ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60};
//转换为`JSON:`复制每个值到`JSON`对象中
json j;
j["name"] = p.name;
j["address"] = p.address;
j["age"] = p.age;
//`...`从`JSON`转换:从`JSON`对象复制每个值
ns::person p {
j["name"].template get<std::string>(),
j["address"].template get<std::string>(),
j["age"].template get<int>()
};
它有效,但太多样板,幸好,有一个更好的方法:
//创建人员
ns::person p {"Ned Flanders", "744 Evergreen Terrace", 60};
//转换:人`->JSON`
json j = p;
std::cout << j << std::endl;
//`{"`地址`":"744`常青露台`","`年龄`":60,"`名`":"`内德.弗兰德斯`"}`转换`:json->`人
auto p2 = j.template get<ns::person>();
//就是这样
assert(p == p2);
Basic usage
只需要提供两个
函数:
using json = nlohmann::json;
namespace ns {
void to_json(json& j, const person& p) {
j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}};
}
void from_json(const json& j, person& p) {
j.at("name").get_to(p.name);
j.at("address").get_to(p.address);
j.at("age").get_to(p.age);
}
} //名字空间`ns`
就这样!用你的类型调用json
构造器时,调用自动你的自定义to_json
方法.同样,当调用模板get
或get_to(your_type&)
时,调用from_json
方法.
1,这些方法必须在类型
的名字空间(可为全局
名字空间)中,否则库找无法到它们(此例中,在定义了person
的ns
名字空间中).
2,使用templateget
时,your_type
必须是DefaultConstructible
.
3,在函数from_json
中,使用函数at()
而不是操作符[]
访问对象值.如果键不存在,则at
会触发可处理的异常,而operator[]
表现出未定义行为.
4,你不需要为STL
类型(如std::vector
)添加序化程序或反序化程序:库已实现了.
如果你只想序化/反序化某些结构,则to_json/from_json
函数可是很多样板.
有两个宏
可让你的生活更轻松,只要你(1)
想用JSON
对象作为序化,并且(2)
想用成员变量名作为该对象中的对象键:
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(name,member1,member2,...)
创建在要为其代码的类/结构
的名字空间默认义.
NLOHMANN_DEFINE_TYPE_INTRUSIVE(name,member1,member2,...)
创建在要为其代码的类/结构
中定义.此宏还可访问私有成员
.
在这两个宏中,第一个参数是类/结构名
,其余所有参数都是命名成员.
可用以下命令
创建上述人员
结构的to_json/from_json
函数:
namespace ns {
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person, name, address, age)
}
下面是需要NLOHMANN_DEFINE_TYPE_INTRUSIVE
的私有成员的示例:
namespace ns {
class address {
private:
std::string street;
int housenumber;
int postcode;
public:
NLOHMANN_DEFINE_TYPE_INTRUSIVE(address, street, housenumber, postcode)
};
}
首先,看看转换机制
工作原理:
该库使用JSON
序化程序转换类型为json.nlohmann::json
的默认序化程序是nlohmann::adl_serializer(ADL
表示参数依赖查找).是这样实现的(简化):
template <typename T>
struct adl_serializer {
static void to_json(json& j, const T& value) {
//调用T名字空间中的`"to_json"`方法
}
static void from_json(const json& j, T& value) {
//一样的,但使用`"from_json"`方法
}
};
当你控制类型
的名字空间时,此序化程序工作正常.但是,boost::optional
或std::filesystem::path(C++17)
劫持boost
名字空间是非常糟糕的,向std
添加模板特化以外的内容是非法的.
要解决,你需要在nlohmann
名字空间中添加adl_serializer
的特化,下面是一例:
//部分特化(完全特化也有效)
namespace nlohmann {
template <typename T>
struct adl_serializer<boost::optional<T>> {
static void to_json(json& j, const boost::optional<T>& opt) {
if (opt == boost::none) {
j = nullptr;
} else {
j = *opt; //这调用`adl_serializer::to_json`它找在T的名字空间中到`to_json`的自由函数!
}
}
static void from_json(const json& j, boost::optional<T>& opt) {
if (j.is_null()) {
opt = boost::none;
} else {
opt = j.template get<T>(); //与上述相同,但使用`adl_serializer::from_json`
}
}
};
}
get()
来取不可构造/不可复制类型有一个方法,如果你的类型是MoveConstructible
.你还需要特化adl_serializer
,但有特殊的from_json
重载:
struct move_only_type {
move_only_type() = delete;
move_only_type(int ii): i(ii) {}
move_only_type(const move_only_type&) = delete;
move_only_type(move_only_type&&) = default;
int i;
};
namespace nlohmann {
template <>
struct adl_serializer<move_only_type> {
//注意:返回类型不再是`"void",`该方法只接受一个参数
static move_only_type from_json(const json& j) {
return {j.template get<int>()};
}
//这就是问题所在!你必须提供`to_json`方法!否则,你转换无法将`move_only_type`为`json,`因为你完全特化`adl_serializer`该类型.
static void to_json(json& j, move_only_type t) {
j = t.i;
}
};
}
是的.你可能想看看测试包中的unit-udt.cpp
,以查看一些示例.
如果编写自己的序化程序,则需要执行以下几项操作:
使用与nlohmann::JSON
不同的basic_json
别名(basic_json
的最后模板参数是JSONSerializer
)
在所有to_json/from_json
方法中使用basic_json
别名(或模板参数)
当你需要ADL
时,请使用nlohmann::to_json
和nlohmann::from_json
下面是一例,无需简化,它仅接受大小为<=32
的类型,并使用ADL
.
//如果不需要对T编译时检查,则应使用`void`作为第二个模板参数
template<typename T, typename SFINAE = typename std::enable_if<sizeof(T) <= 32>::type>
struct less_than_32_serializer {
template <typename BasicJsonType>
static void to_json(BasicJsonType& j, T value) {
//想使用`ADL,`并调用正确的`to_json`重载此方法由`adl_serializer`调用,这就是神奇的地方
using nlohmann::to_json; //
to_json(j, value);
}
template <typename BasicJsonType>
static void from_json(const BasicJsonType& j, T& value) {
//这里也是一样
using nlohmann::from_json;
from_json(j, value);
}
};
重新实现序化
程序时要非常小心,如果不注意,可能会栈溢出:
template <typename T, void>
struct bad_serializer
{
template <typename BasicJsonType>
static void to_json(BasicJsonType& j, const T& value) {
//这调用`BasicJsonType::json_serializer::to_json(j,value);`如果`BasicJsonType::json_serializer==bad_serializer...`哎呀!
j = value;
}
template <typename BasicJsonType>
static void to_json(const BasicJsonType& j, T& value) {
//这调用`BasicJsonType::json_serializer::from_json(j,value);`如果`BasicJsonType::json_serializer==bad_serializer...`哎呀!哎呀!
value = j.template get<T>(); //
}
};
默认,枚举值作为整数序化为JSON
.有时候,会导致意外行为.如果在数据序化为JSON
后修改或重排枚举,则稍后反序化的JSON
数据可能未定义或枚举值与最初期望不同.
可更精确地指定给定枚举
,如何映射到JSON
及从JSON
映射,如下:
//示例枚举类型声明
enum TaskState {
TS_STOPPED,
TS_RUNNING,
TS_COMPLETED,
TS_INVALID=-1,
};
//映射任务状态值作为串到`JSON`
NLOHMANN_JSON_SERIALIZE_ENUM( TaskState, {
{TS_INVALID, nullptr},
{TS_STOPPED, "stopped"},
{TS_RUNNING, "running"},
{TS_COMPLETED, "completed"},
})
NLOHMANN_JSON_SERIALIZE_ENUM()
宏为TaskState
类型声明一组to_json()/from_json()
函数,同时避免重复和样板序化
代码.
用法:
//枚举到串`JSON`
json j = TS_STOPPED;
assert(j == "stopped");
//`JSON`串到枚举
json j3 = "running";
assert(j3.template get<TaskState>() == TS_RUNNING);
//枚举的未定义`JSON`值(其中上面的第一个映射项是默认值)
json jPi = 3.14;
assert(jPi.template get<TaskState>() == TS_INVALID );
与上面的任意类型转换一样,
NLOHMANN_JSON_SERIALIZE_ENUM()
必须在枚举类型的名字空间(可是全局名字空间)中声明,否则库找无法到它,它默认为整数序化.
它必须在你使用转换的地方可用(如,必须包含正确的头文件).
使用模板get
时,未定义的JSON
值默认为映射中指定的第一对.请仔细选择此默认对.
如果在映射中多次指定枚举或JSON
值,则在转换为JSON
或从JSON
转换时,返回映射顶部的第一个匹配项.
二进制格式(BSON,CBOR,MessagePack,UBJSON
和BJData
)
虽然JSON
是一个无处不在的数据格式,但它不是一个非常紧凑的格式,适合数据交换,如通过网络.因此,该库支持BSON
(二进制JSON
),CBOR
(简洁二进制对象表示),MessagePack,UBJSON
(通用二进制JSON
规范)和BJData
(二进制JData
),以有效地编码JSON
值为字节向量并解码此类向量.
//创建`JSON`值
json j = R"({"compact": true, "schema": 0})"_json;
//序化为`BSON`
std::vector<std::uint8_t> v_bson = json::to_bson(j);
//`0x1B,0x00,0x00,0x00,0x08,0x63,0x6F,0x6D,0x70,0x61,0x63,0x74,0x00,0x01,0x10,0x73,0x63,0x68,0x65,0x6D,0x61,0x00,0x00,0x00,0x00,0x00,0x00`往返
json j_from_bson = json::from_bson(v_bson);
//序化为`CBOR`
std::vector<std::uint8_t> v_cbor = json::to_cbor(j);
//`0xA2,0x67,0x63,0x6F,0x6D,0x70,0x61,0x63,0x74,0xF5,0x66,0x73,0x63,0x68,0x65,0x6D,0x61,0x00`往返
json j_from_cbor = json::from_cbor(v_cbor);
//序化为消息包
std::vector<std::uint8_t> v_msgpack = json::to_msgpack(j);
//`0x82,0xA7,0x63,0x6F,0x6D,0x70,0x61,0x63,0x74,0xC3,0xA6,0x73,0x63,0x68,0x65,0x6D,0x61,0x00`往返
json j_from_msgpack = json::from_msgpack(v_msgpack);
//序化为`UBJSON`
std::vector<std::uint8_t> v_ubjson = json::to_ubjson(j);
//`0x7B,0x69,0x07,0x63,0x6F,0x6D,0x70,0x61,0x63,0x74,0x54,0x69,0x06,0x73,0x63,0x68,0x65,0x6D,0x61,0x69,0x000x7D`往返
json j_from_ubjson = json::from_ubjson(v_ubjson);
该库还支持来自BSON,CBOR
(字节串)和MessagePack(bin,ext,fixext)
的二进制类型.默认,它们存储为std::vector
,以便在库外部处理.
//有有效负载`0xCAFE`的`CBOR`字节串
std::vector<std::uint8_t> v = {0x42, 0xCA, 0xFE};
//读取值
json j = json::from_cbor(v);
//`JSON`值的类型为`binarytrue`取对存储的二进制值的引用
j.is_binary(); //
//
auto& binary = j.get_binary();
//二进制值没有子类型`(CBOR`没有二进制子类型)错误访问`std::vector`成员函数`20xCA0xFE`设置子类型为`0x10`
binary.has_subtype(); //
//
binary.size(); //
binary[0]; //
binary[1]; //
//
binary.set_subtype(0x10);
//序化为`MessagePack0xD5(fixext2),0x10,0xCA0xFE`
auto cbor = json::to_msgpack(j); //