zoukankan      html  css  js  c++  java
  • Json文件解析(下)

    Json文件解析(下)

    代码地址:https://github.com/nlohmann/json

     

    STL容器转换

    任何序列容器(std::array,std::vector,std::deque,std::forward_list,std::list),其值可以被用于构建JSON值(例如,整数,浮点数,布尔值,字符串类型,或者再次在本节中描述STL容器)可被用于创建JSON阵列。这同样适用于类似的关联容器(std::set,std::multiset,std::unordered_set,std::unordered_multiset),但是在这些情况下,数组中的元素的顺序取决于元素是如何在各个STL容器排序。

    std ::矢量< INT > c_vector { 1,2,3,4 };

    json j_vec(c_vector);

    // [1、2、3、4]

     

    std ::双端队列< 双 > c_deque { 1.2,2.3,3.4,5.6 };

    json j_deque(c_deque);

    // [1.2、2.3、3.4、5.6]

     

    std :: list < bool > c_list { true,true,false,true };

    json j_list(c_list);

    // [true,true,false,true]

     

    std ::修饰符Modifiers < 的int64_t > c_flist { 12345678909876,23456789098765,34567890987654,45678909876543 };

    json j_flist(c_flist);

    // [12345678909876、23456789098765、34567890987654、45678909876543]

     

    std ::阵列< 无符号 长,4 > c_array {{ 1,2,3,4 }};

    json j_array(c_array);

    // [1、2、3、4]

     

    std :: set <std :: string> c_set { “一个”,“两个”,“三个”,“四个”,“一个” };

    json j_set(c_set); //仅使用“一个”的一个条目

    // // [“四个”,“一个”,“三个”,“两个”]

     

    std :: unordered_set <std :: string> c_uset { “一”,“二”,“三”,“四”,“一” };

    json j_uset(c_uset); //仅使用“一个”的一个条目

    // //可能是[“两个”,“三个”,“四个”,“一个”]

     

    std :: multiset <std :: string> c_mset { “一个”,“两个”,“一个”,“四个” };

    json j_mset(c_mset); //使用两个用于“一个”的条目

    //也许[[一个”,“两个”,“一个”,“四个”]

     

    std :: unordered_multiset <std :: string> c_umset { “一”,“二”,“一”,“四” };

    json j_umset(c_umset); //使用两个用于“一个”的条目

    //也许[[一个”,“两个”,“一个”,“四个”]

    同样,任何缔键值容器(std::map,std::multimap,std::unordered_map,std::unordered_multimap),其键可以构造一个std::string,并且其值可以被用于构建JSON值(见上文实施例)可用于创建一个JSON对象。请注意,在使用多图的情况下,JSON对象中仅使用一个键,并且该值取决于STL容器的内部顺序。

    std :: map <std :: string,int > c_map {{ “一个”,1 },{ “两个”,2 },{ “三个”,3 }};

    json j_map(c_map);

    // {“一个”:1,“三个”:3,“两个”:2}

     

    std :: unordered_map < const  char *,double > c_umap {{ “一个”,1.2 },{ “两个”,2.3 },{ “三个”,3.4 }};

    json j_umap(c_umap);

    // {“一个”:1.2,“两个”:2.3,“三个”:3.4}

     

    std :: multimap <std :: string,bool > c_mmap {{ “一个”,true },{ “两个”,true },{ “三个”,false },{ “三个”,true }};

    json j_mmap(c_mmap); //仅使用键“三”的一个条目

    // //也许{“一个”:true,“两个”:true,“三个”:true}

     

    std :: unordered_multimap <std :: string,bool > c_ummap {{ “一”,true },{ “二”,true },{ “三”,false },{ “三”,true }};

    json j_ummap(c_ummap); //仅使用键“三”的一个条目

    // //也许{“一个”:true,“两个”:true,“三个”:true}

    JSON指针和JSON补丁

    该库支持JSON指针RFC 6901)作为解决结构化值的替代方法。除此之外,JSON PatchRFC 6902)允许描述两个JSON值之间的差异-有效地允许Unix已知的patch和diff操作。

    // JSON值

    json j_original = R“( {{

      ” baz“:[” one“,” two“,” three“],

      ” foo“:” bar“

    } )” _json;

     

    //使用JSON指针(RFC 6901)访问成员

    j_original [ “ / baz / 1 ” _json_pointer];

    // “两个”

     

    //一个JSON修补程序(RFC 6902)

    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合并补丁RFC 7386)作为补丁格式。没有使用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值。可以在此处找到有关此建议的更多详细信息。

    //字符串

    std :: string s1 = “世界好!” ;

    json js = s1;

    自动 s2 = js.get <std :: string>();

    //不推荐

    std :: string s3 = js;

    std :: string s4;

    s4 = js;

     

    //布尔

    值bool b1 = true ;

    json jb = b1;

    自动 b2 = jb.get < bool >();

    //不推荐

    bool b3 = jb;

    布尔 b4;

    b4 = jb;

     

    //数字

    int i = 42 ;

    json jn = i;

    自动 f = jn.get < double >();

    //不推荐使用

    double f2 = jb;

    双 f3;

    f3 = jb;

     

    //等

    请注意,char类型不会自动转换为JSON字符串,而是自动转换为整数。必须明确指定转换为字符串:

    char ch = ' A ' ;                       // ASCII值65

    json j_default = ch;                 //存储整数65

    json j_string = std :: string(1,ch);  //存储字符串“ A”

    任意类型转换

    每种类型都可以用JSON序列化,而不仅仅是STL容器和标量类型。通常,会按照以下方式进行操作:

    名称空间 ns {

         // //一个简单的结构,用于对人员进行建模

        struct  person {

            std :: string名称;

            std :: string地址;

            INT年龄;

        };

    }

     

    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 ” ]。得到 <std :: string>(),

        j [ “ address ” ]。得到 <std :: string>(),

        j [ “年龄” ]。获取 < int >()

    };

    可以工作,但是有很多样板...幸运的是,有更好的方法:

    //创建一个人

    ns :: person p { “ Ned Flanders ”, “ 744 Evergreen Terrace ”, 60 };

     

    //转换:person-> json

    json j = p;

     

    std :: cout << j << std :: endl;

    // {“地址”:“ 744 Evergreen Terrace”,“年龄”:60,“名称”:“ Ned Flanders”}

     

    //转换:

     json- > person auto p2 = j.get <ns :: person>();

     

    //就是

    断言(p == p2);

    基本用法

    要使与一种类型一起使用,只需提供两个功能:

    使用 nlohmann :: json;

     

    命名空间 ns {

         void  to_json(json&j,const person&p){

            j = json {{ “ name ”,p。名称 },{ “地址”,第 地址 },{ “年龄”,第 年龄 }};

        }

     

        无效 from_json(const json&j,person&p){

            j。在(“名称”)。get_to(第名);

            j。在(“地址”)。get_to(第地址);

            j。在(“年龄”)。get_to(第年龄);

        }

    } //名称空间ns

    就这样!当json使用类型调用构造函数时,自定义to_json方法将被自动调用。同样,在调用get<your_type>()或时get_to(your_type&),from_json将调用该方法。

    一些重要的事情:

    • 这些方法必须位于类型的名称空间(可以是全局名称空间)中,否则库将无法找到(在本示例中,位于定义的名称空间ns中person)。
    • 在使用这些转换的任何地方,这些方法都必须可用(例如,必须包含适当的标头)。请查看问题1108,了解否则可能发生的错误。
    • 使用时get<your_type>(),your_type 必须DefaultConstructible。(有一种绕过此要求的方法,将在后面介绍。)
    • 在函数中from_json,使用函数at()访问对象值而不是operator[]。万一键不存在,at将引发可以处理的异常,而operator[]表现出未定义的行为。
    • 无需为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函数person可以通过以下方式创建:

    命名空间 ns {

         NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(人,姓名,地址,年龄)

    }

    这是一个NLOHMANN_DEFINE_TYPE_INTRUSIVE需要私人成员的示例:

    命名空间 ns {

         类 地址 {

           私有:

            std :: string street;

            INT housenumber;

            INT邮编;

           

          公开:

            NLOHMANN_DEFINE_TYPE_INTRUSIVE(地址,街道,门牌号,邮政编码)

        };

    }

    如何转换第三方类型?

    这需要更高级的技术。但首先,让看看这种转换机制是如何工作的:

    该库使用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。

    为了解决这个问题,需要adl_serializer在nlohmann名称空间中添加的特殊化,这是一个示例:

    //部分专业化(也可以完全专业化)

    名称空间 nlohmann {

        模板 <类型名 T>

         struct  adl_serializer <boost :: optional <T >> {

            静态 void  to_json(json&j, const boost :: optional <T>&opt){

                如果(opt == boost :: none){

                    j = nullptr ;

                } 其 {

                  j = * opt; //这将调用adl_serializer <T> :: to_json将

                            //发现T的命名空间中的自由函数to_json!

                }

            }

     

            静态 无效 from_json(const json&j,boost :: optional <T>&opt){

                 if(j。is_null()){

                    opt = boost :: none;

                } 其 {

                    选择= j。得到 <T>(); //与上述相同,但

                                      //使用 adl_serializer <T> :: from_json

                }

            }

        };

    }

    如何get()用于非默认的可构造/不可复制类型?

    如果类型为MoveConstructible,则有一种方法。还需要专门化adl_serializer,但要有一个特殊的from_json重载:

    struct  move_only_type {

         move_only_type()= 删除 ;

        move_only_type(int ii):i(ii){}

         move_only_type(const move_only_type&)= delete ;

        move_only_type(move_only_type &&)= 默认值;

     

        诠释 I;

    };

     

    名称空间 nlohmann {

         template <>

         struct  adl_serializer <move_only_type> {

             //注意:返回类型不再是'void',该方法仅

            //使用一个参数

            static move_only_type from_json(const json&j){

                 return {j。get < int >()};

            }

     

            //这就是陷阱!必须提供to_json方法!否则

            //

            将//无法将move_only_type转换为json,因为完全//对该类型的adl_serializer进行了专门化//

             static  void  to_json(json&j,move_only_type t){

                j = t  ;

            }

        };

    }

    可以编写自己的序列化器吗?(高级使用)

    是。可能需要看一下unit-udt.cpp测试套件,以查看一些示例。

    如果编写自己的序列化器,则需要做一些事情:

    • 使用不同的basic_json别名nlohmann::json(的最后一个模板参数basic_json是JSONSerializer)
    • basic_json在所有to_json/ from_json方法中使用别名(或模板参数)
    • 使用nlohmann::to_json以及nlohmann::from_json何时需要ADL

    这是一个示例,没有简化,仅接受大小<= 32的类型,并使用ADL。

    // //应该使用void作为第二个模板参数

    // //如果不需要对T

    template < typename T, typename SFINAE = typename std :: enable_if < sizeof(T)<= 32 > :: type进行编译时检查 >

     struct  less_than_32_serializer {

        模板 <类型名称BasicJsonType>

        静态 void  to_json(BasicJsonType&j,T值){

             //要使用ADL,并

            使用 nlohmann :: to_json调用正确的to_json重载;//这个方法由adl_serializer调用,

                                     //这就是

            to_json(j,value)发生魔术的地方;

        }

     

        template < typename BasicJsonType>

         静态 无效 from_json(const BasicJsonType&j,T&value){

             //

            使用 nlohmann :: from_json进行相同操作;

            from_json(j,值);

        }

    };

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

    模板 < 类型名 T,无效 >

     结构 bad_serializer

    {

        template < typename BasicJsonType>

         static  void  to_json(BasicJsonType&j,const T&value){

           //这将调用BasicJsonType :: json_serializer <T> :: to_json(j,value);

          //如果BasicJsonType :: json_serializer == bad_serializer ...糟糕!

          j =值;

        }

     

        template < typename BasicJsonType>

         static  void  to_json(const BasicJsonType&j,T&value){

           //这将调用BasicJsonType :: json_serializer <T> :: from_json(j,value);

          //如果BasicJsonType :: json_serializer == bad_serializer ...糟糕!

          值= j。模板 获取 <T>(); //哎呀!

        }

    };

    专门进行枚举转换

    默认情况下,枚举值以整数形式序列化为JSON。在某些情况下,这可能会导致不良行为。如果在将数据序列化为JSON之后对枚举进行了修改或重新排序,则较晚的反序列化JSON数据可能是未定义的或与原始预期值不同的枚举值。

    可以更精确地指定给定枚举如何映射到JSON和从JSON映射,如下所示:

    //示例枚举类型声明

    枚举 TaskState {

        TS_STOPPED,

        TS_RUNNING,

        TS_COMPLETED,

        TS_INVALID = -1,

    };

     

    //将TaskState值作为字符串

    NLOHMANN_JSON_SERIALIZE_ENUM 映射到JSON(TaskState,{

        {TS_INVALID,nullptr },

        {TS_STOPPED,“已停止” },

        {TS_RUNNING,“正在运行” },

        {TS_COMPLETED,“已完成”),

    })

    的NLOHMANN_JSON_SERIALIZE_ENUM()宏声明一组to_json()/ from_json()功能型TaskState,同时避免重复和样板序列化代码。

    用法:

    //以字符串形式枚举JSON

    json j = TS_STOPPED;

    断言(j == “ stopped ”);

     

    //枚举的json字符串

    json j3 = “正在运行”;

    断言(j3.get <TaskState>()== TS_RUNNING);

     

    //要枚举的未定义json值(其中,上面的第一个地图项是默认值)

    json jPi = 3.14 ;

    断言(jPi.get <TaskState>()== TS_INVALID);

    就像上面的任意类型转换一样

    • NLOHMANN_JSON_SERIALIZE_ENUM() 必须在枚举类型的名称空间(可以是全局名称空间)中声明,否则库将无法找到,并且将默认为整数序列化。
    • 使用转换的任何地方都必须可用(例如,必须包含适当的标题)。

    其要点:

    • 使用时get<ENUM_TYPE>(),未定义的JSON值将默认为地图中指定的第一对。仔细选择该默认对。
    • 如果在地图中多次指定枚举或JSON值,则在与JSON进行相互转换时,将从地图顶部的第一个匹配项返回。

    二进制格式(BSONCBORMessagePackUBJSON

    尽管JSON是一种无处不在的数据格式,但不是一种非常紧凑的格式,适合通过网络进行数据交换。因此,该库支持  BSON(二进制JSON),CBOR(简明二进制对象表示形式),MessagePackUBJSON(通用二进制JSON规范),以将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

     

    //往返

    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);

     

    //序列化为MessagePack

    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、0x00、0x7D

     

    //往返

    json j_from_ubjson = json :: from_ubjson(v_ubjson);

    该库还支持BSON,CBOR(字节字符串)和MessagePack(bin,ext,fixext)的二进制类型。默认存储std::vector<std::uint8_t>为在库外部进行处理。

    // CBOR字节串与有效载荷为0xCAFE

    的std ::矢量<性病:: uint8_t > V = {的0x42, 0xCA, 0xFE的 };

     

    //读取值

    json j = json :: from_cbor(v);

     

    // JSON值的类型为binary

    j.is_binary(); //正确

     

    //获取对存储的二进制值的引用

    auto&binary = j.get_binary();

     

    //二进制值没有子类型(CBOR没有二进制子类型)

    binary.has_subtype(); //错误

     

    //访问std :: vector <std :: uint8_t>成员函数

    binary.size(); // 2

    binary [ 0 ]; // 0xCA

    binary [ 1 ]; // 0xFE

     

    //将子类型设置为0x10

    二进制。set_subtype( 0x10);

     

    //序列化为MessagePack

    auto cbor = json :: to_msgpack(j); // 0xD5(fixext2),0x10、0xCA,0xFE

    支持的编译器

    尽管已经是2020年,但对C ++ 11的支持仍然很少。当前,已知以下编译器可以工作:

    • GCC 4.8-10.1(可能以后)
    • 铛3.4-10.0(可能以后)
    • Apple Clang 9.1-12.0(可能更高)
    • 英特尔C ++编译器17.0.2(可能更高)
    • Microsoft Visual C ++ 2015 /构建工具14.0.25123.0(以及更高版本)
    • Microsoft Visual C ++ 2017 /生成工具15.5.180.51428(以及更高版本)
    • Microsoft Visual C ++ 2019 /生成工具16.3.1 + 1def00d3d(以及更高版本)

    很乐意了解其编译器/版本。

    请注意:

    • GCC 4.8有一个错误57824):多行原始字符串不能作为宏的参数。不要使用此编译器直接在宏中使用多行原始字符串。
    • Android默认使用非常老的编译器和C ++库。要解决此问题,请将以下内容添加到中Application.mk。这将切换到LLVM C ++库,Clang编译器,并启用C ++ 11和其默认禁用的功能。
    • APP_STL := c++_shared
    • NDK_TOOLCHAIN_VERSION := clang3.6
    • APP_CPPFLAGS += -frtti -fexceptions

    该代码可使用Android NDK,修订版9-11(以及更高版本)和CrystaX的Android NDK版本10 成功编译。

    • 对于在MinGW或Android SDK上运行的GCC 'to_string' is not a member of 'std',可能会发生错误(或类似的for strtod或strtof)。注意,这不是代码问题,而是编译器本身的问题。在Android上,请参见上文以使用较新的环境进行构建。对于MinGW,请参考此站点此讨论以获取有关如何修复此错误的信息。对于Android NDK的使用APP_STL := gnustl_static,请参考此讨论
    • #error指令拒绝不支持的GCC和Clang版本。可以通过定义关闭JSON_SKIP_UNSUPPORTED_COMPILER_CHECK。请注意,在这种情况下,不希望获得任何支持。

    TravisAppVeyorGitHub ActionsCircleCI当前在持续集成中使用编译器

  • 相关阅读:
    SGU 456 Annuity Payment Scheme
    SPOJ AMR10F Cookies Piles
    poj 2823 Sliding Window (单调队列)
    (bc #45) A
    cf 442C. Artem and Array
    cf 442B Andrey and Problem
    cf 443B Kolya and Tandem Repeat
    (BC 一周年) hdu 5312 Sequence
    (BC 一周年)hdu 5311 Hidden String
    (BC 一周年)hdu 5310 Souvenir
  • 原文地址:https://www.cnblogs.com/wujianming-110117/p/13300941.html
Copyright © 2011-2022 走看看