低版本CJson反序列化方案
现在的API接口普遍使用Json语法来描述,API的返回通常使用类似这样的结构:{ "code": 0, "message": "", "data": {...} }
即所有API都有code和message属性以表示成功或错误,它和业务是无关的;其它属性/数据则在data中定义,它和业务是相关的。
这种情况在C# / JAVA 中做反序列化时一般用泛型表示,如:class MyAPI { public int x; public int y; } class ApiResponse { public int code; public string message; public T data; } ApiResponse resp = JsonConvert.DeserializeObject>(...);
C++的处理方案由于RTTI(Runtime Type Information运行时类型信息)并不包含属性名等信息,无法通过反射找到类/结构的属性,因此无法实现C# / JAVA那种看起来比较优雅的写法。
github上解决这个问题的项目很多,但它们许多要求使用c++17或20以上的版本,由于我的项目限制只能使用c++14,因此我只看c++11或14的解决方案。
这些项目的解决方案大体有两类:
1.提供更友好的读写属性的方法,比如通过["..."]["..."]层层嵌套,让用户更容易读写属性,但是它并没有完成在对象/结构实体上的映射和赋值,相当于这个工作得用户来做,因此它更像是只提供了一些序列化/反序列化的辅助方法,而不是完整的json和对象转化的解决方案,标星超过3万的https://github.com/nlohmann/json就是这种情况;
2.允许用户用通常的写法去定义类/结构,再通过宏去标识需要和json做映射的属性,完成类似其他语言的反射机制,当然它是在编译期实现属性定位而不是其他语言的运行期。
方案2在使用成本上相较方案1要小很多,也更符合主流方案的习惯。我看了几个项目,最后选了json_struct(https://github.com/jorgen/json_struct),它的解决方案是:
对于一个json串:{ "One" : 1, "Two" : "two", "Three" : 3.333 }
定义一个类:struct JsonObject { int One; std::string Two; double Three; JS_OBJ(One, Two, Three); };
JS_OBJ是用于解决反射问题的宏,也可以定义在类外面:struct JsonObject { int One; std::string Two; double Three; }; JS_OBJ_EXT(JsonObject, One, Two, Three);
反序列化:JS::ParseContext context(json_data); JsonObject obj; context.parseTo(obj);
由此可见,除了所有方案都要写的类/结构定义之外,为反序列化要做的也就只写几行代码而已,相对方案1是简单了很多的,与C# / JAVA的解决方案相当。
回到最初的需求:{ "code": 0, "message": "", "data": {...} }
这个结构是嵌套的,无法使用类似于C# / JAVA 的写法:class MyAPI { public int x; public int y; } class ApiResponse { public int code; public string message; public T data; } ApiResponse resp = JsonConvert.DeserializeObject>(...);
json_struct的文档和examples中也没有提供明确的案例可以参考。
我试了一下,可以这样来定义:struct ApiResponse { int code; string message; JS_OBJ(code, message); } struct MyApiData { int x; int y; JS_OBJ(x, y); } struct MyAPI : public ApiResponse { MyApiData data; JS_OBJ(data); }
然后这样解析就可以了:JS::ParseContext context(...); MyAPI myApi; context.parseTo(myApi);
上述写法,相较C# / JAVA,C# / JAVA是用ApiResponse来包MyAPI(泛型传参),C++是用MyAPI来包ApiResponse(子类继承),并将子属性data从MyAPI拆出来,相当于多定义了一级,从语法上似乎稍微累赘一点,但整体结构还是清晰直观的。