xpack
====
* 用于在C++结构体和json/xml之间互相转换, bson在[xbson](https://github.com/xyz347/xbson)中支持。
* 只有头文件, 无需编译库文件,所以也没有Makefile。
* 具体可以参考example的例子
------
* [基本用法](#基本用法)
* [容器支持](#容器支持)
* [FLAG](#flag)
* [别名](#别名)
* [位域](#位域)
* [继承](#继承)
* [枚举](#枚举)
* [自定义编解码](#自定义编解码)
* [union](#union)
* [不定类型](#不定类型)
* [数组](#数组)
* [第三方类和结构体](#第三方类和结构体)
* [格式化缩进](#格式化缩进)
* [XML数组](#xml数组)
* [Qt支持](#qt支持)
* [重要说明](#重要说明)
基本用法
----
- 结构体后面用XPACK宏包含各个变量,XPACK内还需要一个字母,不同字母的意义请参考[FLAG](#flag)
- 用xpack::json::encode把结构体转json
- 用xpack::json::decode把json转结构体
```C++
#include <iostream>
#include "xpack/json.h" // Json包含这个头文件,xml则包含xpack/xml.h
using namespace std;
struct User {
int id;
string name;
XPACK(O(id, name)); // 添加宏定义XPACK在结构体定义结尾
};
int main(int argc, char *argv[]) {
User u;
string data = "{\"id\":12345, \"name\":\"xpack\"}";
xpack::json::decode(data, u); // json转结构体
cout<<u.id<<';'<<u.name<<endl;
string json = xpack::json::encode(u); // 结构体转json
cout<<json<<endl;
return 0;
}
```
容器支持
----
目前支持下列容器(std)
- vector
- set
- list
- map<string, T>
- map<integer, T> // 仅JSON,XML不支持
- unordered_map<string, T> (需要C++11支持)
- shared_ptr (需要C++11支持)
FLAG
----
宏XPACK里面,需要用字母将变量包含起来,比如XPACK(O(a,b)),XPACK可以包含多个字母,每个字母可以包含多个变量。目前支持的字母有:
- X。格式是X(F(flag1, flag2...), member1, member2,...) F里面包含各种FLAG,目前支持的有:
- 0 没有任何FLAG
- OE omitempty,encode的时候,如果变量是0或者空字符串或者false,则不生成对应的key信息
- EN empty as null, 用于json的encode,OE是直接不生成empty的字段,EN则是生成一个null
- M mandatory,decode的时候,如果这个字段不存在,则抛出异常,用于一些id字段。
- ATTR attribute,xml encode的时候,把值放到attribute里面。
- C。格式是C(customcodec, F(flag1,flags...), member1, member2,...)用于自定义编解码函数,详情请参考[自定义编解码](#自定义编解码)
- O。等价于X(F(0), ...) 没有任何FLAG。
- M。等价于X(F(M),...) 表示这些字段是必须存在的。
- A。[别名](#别名),A(member1, alias1, member2, alias2...),用于变量和key名不一样的情况
- AF。带FLAG的[别名](#别名),AF(F(flag1, flag2,...), member1, alias1, member2, alias2...)
- B。[位域](#位域),B(F(flag1, flag2, ...), member1, member2, ...) **位域不支持别名**
- I。[继承](#继承),I(baseclass1, baseclass2....),里面放父类
- E。[枚举](#枚举):
- 如果编译器支持C++11,不需要用E,枚举可以放X/O/M/A里面。
- 否则枚举只能放E里面,不支持别名
别名
----
- 用于变量名和key名不一致的场景
- 格式是A(变量,别名....)或者AF(F(flags), 变量,别名....),别名的格式是"x t:n"的格式
- x表示全局别名,t表示类型(目前支持json、xml、bson),n表示类型下的别名
- 全局别名可以没有,比如`json:_id`是合法的
- 类型别名可以没有,比如`_id`是合法的
- 有类型别名优先用类型别名,否则用全局别名,都没有,则用变量名
``` C++
#include <iostream>
#include "xpack/json.h"
using namespace std;
struct Test {
long uid;
string name;
XPACK(A(uid, "id"), O(name)); // "uid"的别名是"id"
};
int main(int argc, char *argv[]) {
Test t;
string json="{\"id\":123, \"name\":\"Pony\"}";
xpack::json::decode(json, t);
cout<<t.uid<<endl;
return 0;
}
```
位域
----
- 使用"B"来包含位域变量,**位域不支持别名**
``` C++
#include <iostream>
#include "xpack/json.h"
using namespace std;
struct Test {
short ver:8;
short len:8;
string name;
XPACK(B(F(0), ver, len), O(name));
};
int main(int argc, char *argv[]) {
Test t;
string json="{\"ver\":4, \"len\":20, \"name\":\"IPv4\"}";
xpack::json::decode(json, t);
cout<<t.ver<<endl;
cout<<t.len<<endl;
return 0;
}
```
继承
----
- 使用"I"来包含父类。需要用到父类的变量就包含,用不到可以不包含。
- 父类的父类也需要包含,比如class Base; class Base1:public Base; class Base2:public Base1;那么在Base2中需要I(Base1, Base)
- 父类也需要定义XPACK/XPACK_OUT宏。
```C++
#include <iostream>
#include "xpack/json.h"
using namespace std;
struct P1 {
string mail;
XPACK(O(mail));
};
struct P2 {
long version;
XPACK(O(version));
};
struct Test:public P1, public P2 {
long uid;
string name;
XPACK(I(P1, P2), O(uid, name));
};
int main(int argc, char *argv[]) {
Test t;
string json="{\"mail\":\"pony@xpack.com\", \"version\":2019, \"id\":123, \"name\":\"Pony\"}";
xpack::json::decode(json, t);
cout<<t.mail<<endl;
cout<<t.version<<endl;
return 0;
}
```
枚举
----
- 如果编译器支持C++11,则枚举和普通变量名一样,放X/O/M/A里面皆可。
- 否则需要放到E里面,格式是E(F(...), member1, member2, ...)
```C++
#include <iostream>
#include "xpack/json.h"
using namespace std;
enum Enum {
X = 0,
Y = 1,
Z = 2,
};
struct Test {
string name;
Enum e;
XPACK(O(name), E(F(0), e));
};
int main(int argc, char *argv[]) {
Test t;
string json="{\"name\":\"IPv4\", \"e\":1}";
xpack::json::decode(json, t);
cout<<t.name<<endl;
cout<<t.e<<endl;
return 0;
}
```
自定义编解码
----
应用场景
- 有些基础类型想用自定义方式编码,比如用字符串的方式来编码整数/浮点数
- 部分类型可能不想按结构体变量逐个编码,比如定义了一个时间结构体:
```C++
struct Time {
long ts; //unix timestamp
};
```
并不希望编码成{"ts":1218196800} 这种格式,而是希望编码成"2008-08-08 20:00:00"这种格式。
这里有两种方式:
- 使用xtype,可以参考[例子](example/xtype.cpp)
- 用C来包含需要自定义编解码的变量(下面简称C方法),可以参考[例子](example/custom.cpp)
两种方法本质上都是自己去实现encode/decode,但是有以下区别:
- xtype是类型级别的,也就是一旦某个类型用xtype封装之后,那么自定义的encode/decode就对这个类型生效。xtype无法作用于基本类型(int/string等)
- C方法能支持基本类型(int/string等)和非基本类型,但是仅作用于用C包含的变量,比如int a;int b; O(a), C(custome_int, F(0), b);那么a还是用默认的编解码,b才是用自定义的编解码。
1. xtype优先于XPACK宏,也就是定义了xtype的,会优先使用xtype的encode/decode
2. C方法优先于xtype,也就是用C包含的变量,一定会用C里面指定的编解码方法。
用这两个特性,可以实现一些比较灵活的编解码控制,比如这个[例子](example/xtype_advance.cpp)实现了一个根据变量情况来编码的功能,如果Sub.type==1则encode seq1,否则encode seq2. `__x_pack_decode`和`__x_pack_encode`是XPACK宏给结构体添加的decode/encode函数,自定义编解码函数可以通过这些函数调用xpack默认的编解码功能。
union
----
可以使用[自定义编解码](#自定义编解码)来处理联合体,可以参考[范例](example/union1.cpp)
数组
----
- decode的时候如果元素个数超过数组的长度,会截断
- char数组按有\0结束符处理
```C++
#include <iostream>
#include "xpack/json.h"
using na