2309C++nlohmann数格示例2

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_ImplicitConversionsOFF来实现此目的.

//串
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,这些方法必须在类型的名字空间(可为全局名字空间)中,否则库找无法到它们(此例中,在定义了personns名字空间中).
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::optionalstd::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_jsonnlohmann::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,UBJSONBJData)
虽然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); //

你可能感兴趣的:(c++,cpp,c++,开发语言)