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当前在持续集成中使用编译器

posted @ 2020-07-14 19:03  吴建明wujianming  阅读(1137)  评论(0编辑  收藏  举报