国产福利:C++对象的json序列化扩展

4 篇文章 0 订阅
2 篇文章 0 订阅
本文介绍了一种C++库,实现了基于cJSON的JSON序列化和反序列化功能,用于将C++结构体和类对象便捷地转换为JSON字符串,适用于国产化开发环境。通过宏扩展简化了编程过程,但存在一些局限性,如数组处理和成员变量数量限制。
摘要由CSDN通过智能技术生成

先上链接:https://pan.baidu.com/s/1Tb7Vv7osGqdeG0ZoKOU6_g?pwd=btn5

2024.04.09:github上也更新了,链接在:https://github.com/neoatomic-repo/cxx-json-serialize

尽管本文实现的扩展是针对C++应用的,大家都会用得上;但从实际业务场景上,也是为转到从事国产化开发的同学们,提供一个便利,你们懂的。
如果你看完了本文、并且下载源码包觉得用着OK,记得点赞让我知道哈(git不容易连上:()。

准确的讲,本文的主题是“JSON序列化:应用于C++对象”。 
本应用扩展的实现基于c++11、cJSON。(有了它,可以替换QJson了 ,比如QPluginLoader里面的json操作就挺麻烦的)。
CJS前缀: c++ JSON Serialize

背景与问题

在C++中,即便于使用cJSON、或者QJson等,一般也只是能从一个json字符串,得到一系列层次化的json键值对。
比如,以jsonStr = "{\"name\":\"Rich\",\"id\":123}"这串代表json格式的字符串为例,使用cJSON库中的cJSON_Parse进行解析后,会得到一个cJSON对象jsonObj,它的成员状态为:

jsonObj["name"] == "Rich"
jsonObj["id"] == 123

然而,我们其实更需要的是像java中的一些工具库那样(比如阿里的fastjson等),能把jsonStr直接反序列化为我们在业务层定义的某个类/结构体对象实例;或者把某个类/结构体对象实例,直接序列化为jsonStr字符串。
经常使用C++的同学,可能对java里的序列化/反序列化的使用不熟悉,从而惧怕或者不明就里。然而,一方面基于反射实现的这种便捷,综合效率上,已经经过了长久验证;另一方面,更明朗的,我们以fastjson为例来说明,其在java里的基本使用如下:

package com.neoatomic.example;
  
import com.alibaba.fastjson.*;
  
public class App {
    // 假设定义了一个(java里)的类JPerson,对其进行序列化操作:
    public static JPerson {
        public String name;
        public Integer id;
    }
  
    public static void main(String[] args) {
        JPerson personInfo;
        personInfo.name = "Poly";
        personInfo.id = 12;
        System.out.println(JSON.toJSONString(personInfo)); // 这里输出:"{\"name\":\"Poly\",\"id\":12}"
  
        String jsonStr = "{\"name\":\"Rich\",\"id\":123}";
        personInfo = JSON.parseObject(jsonStr, JPerson.class);
        // 现在,personInfo.name.equals("Rich"); personInfo.id == 123
    }
}

如上,在java里,由于有反射,所以fastjson能够提供便利的接口toJSONString、parseObject,以方便的对某个对象进行序列化操作(或者将一串json字符串反序列化以填充这个对象的成员变量值)。
  
所以,问题来了,如何在C++里也像java一样,便利的对结构体(或者类)对象进行json的序列化操作呢

方案

由于C++里,目前的标准库仍然没有反射,并且为了让代码在遗留的历史OS上容易的编译,我们经常还是会使用c++11、c++14,所以,实现这种便利,不应该使用最新的C++标准库,否则仍然会把麻烦留给使用者。
随着时间的推移,10多年前的C++11,也终于有了相对广泛的使用,所以基于c++11、cJSON,本代码实现了如上java中的类似功能:即对C++里的结构体/类对象,实现对其的json序列化操作(序列化与反序列化)。
  
然而,由于没有反射,想在C++里“无侵入”的实现、使用这种便捷,由业务层上的简单实现是不可能的;所以,我们采用了以下方法,来实现这种便利的“扩展”:在结构体/类里声明一个宏扩展。定义结构体/类后,“顺便的”在其定义内,添加上这个扩展宏(以及宏需要的参数)。
  
相比较以前的实现,本次使用了更简洁的宏扩展方式来达成,所以,仍然无需为这种扩展担忧,因为它的学习曲线几乎是平的。这也是CV实现在心理上带来的一个积极效果。重点在于看一看下面的这个C++示例:

#include <iostream>
#include "cxxstruct2json.h"
  
struct TPerson {
    std::string name;
    int id;
  
    CJS_JSON_SERIALIZE(TPerson, name, id)
};
  
int main() {
    TPerson personInfo;
    personInfo.name = "Rich";
    personInfo.id = "123";
  
    std::cout << personInfo.__cjsToJsonString() << std::endl; // 这里输出:"{\"name\":\"Rich\",\"id\":123}"
  
    std::string strJson = "{\"name\":\"Poly\",\"id\":12}";
    personInfo.__cjsFromJsonString(strJson);
    // 现在,personInfo.name == "Poly"; personInfo.id == 12
  
    return 0;
}

如上示例,在结构体TPerson中,使用扩展宏,传入结构体名(它主要起一个标识作用,或者想获取类的类型名等信息时,可以使用)、需要json序列化操作的成员变量名后,即完成了这种扩展。


更多的,这种宏扩展,并不会改变结构体在sizeof时的大小,即即便于你仍然想通过网络直接(将结构体转换为char*)发送结构体对象实例代表的二进制数据,接收方在接收到时,定义一个没有此扩展宏的同结构体后,用来接收这段二进制流,仍然能原生的直接强制转换为这个结构体对象实例。(这个在挺多存量C++业务场景是需要的,交互方的系统是什么情况我们其实是不清楚的)

使用注意

一、支持的类型

在几年前的一版实现中,我们使用了冗余的模式,如

OLD_CJS_JSON_SERIALIZE(StructName, intFromJsonFuc, numToJsonFunc, x,
                                      strFromJsonFuc, strToJsonFunc, y)


intToJsonFunc、numToJsonFunc、strFromJsonFuc、strToJsonFunc为一些预置的方法,用它们来正确的对结构体的成员变量x、y等进行序列化操作。
尽管这种冗余,在使用几次后,就会非常的熟练,然而,它也有一些限制,比如,一些编译器,对可变宏参数的数量限制是128个,上面这种冗余,相当于三分之二的参数不能当成员变量使用,当然实际业务中,我们很少会直接定义一个成员变量数量巨大的结构体,但是,书写上仍然是有些冗余。另外,不断的改进,(最终向java等的便利靠齐),也是我们继续探索的原因。
  
所以,为了去掉这种冗余和继续探索,我们需要对结构体/类成员变量使用的常见的基本类型,进行模板实现。你在使用中进行成员变量声明时,可以使用如下的类型(与cJSON对应):
1、基础类型。
char, unsigned char, short, unsigned short, int, unsigned int, long, unsigned long,
long long, unsigned long long, float, double, bool
  
2、基础类型的数组类型。
char[](将其转换为char*字符串,或者使用strcpy进行赋值)
  
3、std::string类型。
4、std::vector类型。std::vector的元素类型,覆盖了上述1中的基础类型,以及使用了CJS_JSON_SERIALIZE扩展宏的结构体/类。
5、std::map<std::string, T>类型。其中T包含上述1中的基础类型,以及使用了CJS_JSON_SERIALIZE扩展宏的结构体/类。
6、指针。使用了CJS_JSON_SERIALIZE扩展宏的结构体/类指针,目前需要先在外部申请资源后,再进行序列化操作。
  

二、成员变量的数量

本实现中,我们限定了最多支持32个成员变量的json序列化操作。当然,也可以按照既有的格式,对CJS_CNT1等宏做扩展,以便支持更多数量的成员变量。
  

三、杂项

1、__cjsToJsonString的实现上,是不需要依赖于cJSON的:通过循环、递归即可达成结构体/类对象向json字符串的序列化操作。因为本扩展几乎都是对已有功能的整合和包装,所以在这个实现上,也使用了标准库的to_string等方法,没有像一些同类的其他库使用了自造轮子来实现。
2、std::map<整型类型, T>类型的未支持。通过模板实现std::map<KeyType, ValueType>以及整型类型的特例化,是可能的,但是,由于json键是以字符串来表示的,所以本扩展也未实现。

使用步骤

1、在你的工程设置里,使用C++11及以上的标准;
2、在你的源代码里,包含本功能有关的4个源代码文件:
    cJSON.h    cJSON.c    cxxJsonSerialize.h    cxxJsonSerialize.h
3、在定义结构体/类的.h/.cpp文件开始,#include "cxxJsonSerialize.h",以包含本扩展对外的头文件(注意路径);
4、定义结构体/类,在其中使用宏CJS_JSON_SERIALIZE(<结构体/类名>, 成员变量名1, 成员变量名2, ....)
5、使用结构体/类时,声明一个结构体/类对象obj,使用obj.__cjsFromJsonString(<strJson>)、obj.__cjsToJsonString()实现结构体/类对象的序列化操作。
  
简单的实例见方案里的示例。
  
嵌套的复杂实例,如下:

#include <iostream>
#include "utils/json/cxxJsonSerialize.h"
  
struct TBaseType {
    char c;
    unsigned char uc;
    short s;
    unsigned short us;
    int i;
    unsigned int ui;
    long l;
    unsigned long ul;
    long long ll;
    unsigned long long ull;
    float f;
    double d;
    bool b;
    char chStr[64];
    std::string str;
  
    CJS_JSON_SERIALIZE(TBaseType, c, uc, s, us, i, ui,
            l, ul, ll, ull, f,
            d, b, chStr, str)
};
  
struct TVectorType {
    std::vector<char> vecC;
    std::vector<unsigned char> vecUc;
    std::vector<short> vecS;
    std::vector<unsigned short> vecUs;
    std::vector<int> vecI;
    std::vector<unsigned int> vecUi;
    std::vector<long> vecL;
    std::vector<unsigned long> vecUl;
    std::vector<long long> vecLl;
    std::vector<unsigned long long> vecUll;
    std::vector<float> vecF;
    std::vector<double> vecD;
    std::vector<bool> vecB;
    std::vector<std::string> vecStr;
  
    CJS_JSON_SERIALIZE(TVectorType, vecC, vecUc, vecS, vecUs, vecI,
            vecUi, vecL, vecUl, vecLl, vecUll,
            vecF, vecD, vecB, vecStr);
};
  
struct TStringMapType {
    std::map<std::string, char> mapC;
    std::map<std::string, unsigned char> mapUc;
    std::map<std::string, short> mapS;
    std::map<std::string, unsigned short> mapUs;
    std::map<std::string, int> mapI;
    std::map<std::string, unsigned int> mapUi;
    std::map<std::string, long> mapL;
    std::map<std::string, unsigned long> mapUl;
    std::map<std::string, long long> mapLl;
    std::map<std::string, unsigned long long> mapUll;
    std::map<std::string, float> mapF;
    std::map<std::string, double> mapD;
    std::map<std::string, bool> mapB;
    std::map<std::string, std::string> mapStr;
  
    CJS_JSON_SERIALIZE(TStringMapType, mapC, mapUc, mapS, mapUs, mapI,
            mapUi, mapL, mapUl, mapLl, mapUll,
            mapF, mapD, mapB, mapStr)
};
  
struct TTreeItem {
    long long id;
    std::string name;
  
    std::vector<TTreeItem> children;
  
    CJS_JSON_SERIALIZE(TTreeItem, id, name, children)
};
  
struct TTree {
    long long id;
    std::string name;
    std::vector<TTreeItem> items;
  
    CJS_JSON_SERIALIZE(TTree, id, name, items)
};
  
struct TPortfolio{
    int id;
    std::string name;
    TBaseType baseType;
    std::vector<TBaseType> vecBasetype;
    std::map<std::string, TBaseType> mapBasetype;
    TTree tree;
    std::map<std::string, TTree> mapTree;
    TTree *pTree;
  
    TPortfolio() {
        pTree = new TTree();
    }
  
    ~TPortfolio() {
        delete pTree;
    }
  
    CJS_JSON_SERIALIZE(TPortfolio, id, name, baseType, vecBasetype, mapBasetype,
            tree, mapTree, pTree)
};
  
class CPortfolio {
    int id;
    std::string name;
    TBaseType baseType;
    std::vector<TBaseType> vecBasetype;
    std::map<std::string, TBaseType> mapBasetype;
    TTree tree;
    std::map<std::string, TTree> mapTree;
    TTree *pTree;
  
public:
    CPortfolio() {
        pTree = new TTree();
    }
  
    ~CPortfolio() {
        delete pTree;
    }
  
    CJS_JSON_SERIALIZE(CPortfolio, id, name, baseType, vecBasetype, mapBasetype,
            tree, mapTree, pTree)
};
  
int main() {
    TPortfolio portfolioObj;
    portfolioObj.name = "Rich";
    portfolioObj.id = 12;
    portfolioObj.vecBasetype.push_back(TBaseType());
    portfolioObj.vecBasetype.push_back(TBaseType());
    portfolioObj.vecBasetype.push_back(TBaseType());
    portfolioObj.tree.items.push_back(TTreeItem());
    portfolioObj.tree.items[0].children.push_back(TTreeItem());
  
    // struct object TO json string
    std::string strJson = portfolioObj.__cjsToJsonString();
    std::cout << strJson << std::endl;
  
    // json string TO struct object
    portfolioObj.__cjsFromJsonString(strJson);
  
    std::cout << std::endl;
    strJson = portfolioObj.__cjsToJsonString();
    std::cout << strJson << std::endl;
  
    // class object TO json string
    CPortfolio clsPortfolioObj;
    std::cout << std::endl;
    strJson = clsPortfolioObj.__cjsToJsonString();
    std::cout << strJson << std::endl;
  
    return 0;
}

局限性

本组件对业务应用结构体/类提供的扩展宏CJS_JSON_SERIALIZE,主要是为了对应cJSON中的若干类型,更加方便的在开发时使用json序列化;所以除了扩展cJSON(或者QJson)等键值化json功能组件,其余有挺多局限性的(C++的魔幻语法太多了),不限于以下:
1、基础类型的数组。对int、double之类的基础类型,由于与const T&、const T*等模板冲突,目前仍然只能使用std::vector<int>,或者std::vector<double>等对应std::vector类型来实现。
2、多维数组。本扩展没有考虑多维数组的实现,(因为一维数组也没有实现好),所以,使用时需要折衷。从过去业务经验来看,多维数组未曾被使用。
3、最多支持32个成员变量的json序列化操作。这个容易扩展。
4、不支持位结构。尽管通过一定的改造能够实现,但目前未实现,好在目前需要json的场景里(我们主要是对应cJSON的几种类型),极少使用位结构(在spring应用里有吗?)
5、不支持静态成员变量。

未来

1、局限性中的问题,尝试解决?(或许是模板匹配问题)
2、更多数量的成员变量支持。这个是容易的。
3、c99中宏扩展的实现。这个是繁琐的,由于C语言里没有重载、成员方法等,所以,需要做更多的工作,且扩展宏的形式可能都是不一样的。

  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值