• 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"}]`
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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"}}`
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    隐式转换

    可隐式转换支持类型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;
    //等.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    注意,char类型不会自动转换JSON串,而是转换为整数.必须显式指定转换串:

    char ch = 'A';                       //`ASCII`值`65`存储整数`65`存储串`"A"`
    json j_default = ch;                 //
    json j_string = std::string(1, ch);  //
    
    • 1
    • 2
    • 3

    任意类型转换

    每个类型都可在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>()
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    它有效,但太多样板,幸好,有一个更好的方法:

    //创建人员
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    只需要提供两个函数:

    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`
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    就这样!用你的类型调用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)
    }
    
    • 1
    • 2
    • 3

    下面是需要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)
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    转换第三方类型

    首先,看看转换机制工作原理:

    该库使用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"`方法
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    当你控制类型的名字空间时,此序化程序工作正常.但是,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`
                }
            }
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    如何默认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;
            }
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    可编写自己的序化程序吗(高级使用)

    是的.你可能想看看测试包中的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);
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    重新实现序化程序时要非常小心,如果不注意,可能会栈溢出:

    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>(); //
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    特化枚举转换

    默认,枚举值作为整数序化为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"},
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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 );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    与上面的任意类型转换一样,
    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    该库还支持来自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); //
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    基于图搜索的规划算法之A*家族(五):D* 算法
    java并发编程 守护线程 用户线程 main
    Entertainment in MAC(Round 932)
    Frp(内网穿透)服务部署
    工业RFID系统识别原理
    【java】idea可以连接但看不到database相关的files
    介绍五个很实用的IDEA使用技巧
    LeetCode_递归_中等_397.整数替换
    【ROS进阶篇】第九讲 基于Rviz和Arbotix控制的机器人模型运动
    Mac如何打开企业微信内置浏览器控制台
  • 原文地址:https://blog.csdn.net/fqbqrr/article/details/133210789