C++ 轻量级对象JSON序列化实现详情

目录
  • 1、设计思路
  • 2、匹配基本类型的Unmarshal模板
  • 3、匹配stl容器/其他第三方类库的Unmarshal模板
  • 4、匹配自定义struct/class的Unmarshal模板
  • 5、测试

背景:

在项目里经常遇到对象和json字符串的相互转换这类问题,在大多数程序里,一般这个问题都比较有比较好的解决方法,往往一个函数搞定。但是到了c++这边却需要我们手撸json库一个一个字段初始化/序列化。如果这时候有一个函数 可以一行代码 unmarshal /marshal 对象岂不是很方便?本文以jsoncpp库为基础,设计这样一个可以支持这种功能的函数,下面进入正题~

1、设计思路

unmarshal 为例,我们最终的函数是打造两个这样的模板函数 :

一个从string josn直接反序列化对象,一个从jsoncpp库的json对象,反序列化对象。

template<typename T> bool Unmarshal(T& obj,const string& json_str);
template<typename T> bool Unmarshal(T& obj,const Json::Value& json_obj_root);

由于json是具有自递归结构的,所以在设计时,应该也是以递归的方式解决复杂的组合类,我们可以简单的把程序中的变量分为下面几类:

这样我们只需要把这几个场景的 Unmarshal实现了,整体的Unmarshal也就实现了。模板设计的类图应该和我们的分类相对应:

在实现中要注意以下几点:

  • 每个分类的Unmarshal模板具有排他性,也就是说基本类型在编译期间只能匹配解析基本类型的模板
  • 由于1的保证和这些模板函数名称都是Unmarshal,所以他们之间可以相互嵌套调用对方。
  • 指针、和原生数组会涉及到空间分配和长度检测会使得情况变得复杂,本次设计支持的范围不包含对指针、原生数组的支持。

2、匹配基本类型的Unmarshal模板

//只能解析基本类型 int long bool float double string 的一组模板
/*
 * 其实可声明为重载函数,声明为模板只是为了可以放在头文件中
 * 不能声明为 template <typename T> bool Unmarshal(int& obj,const Json::Value &root);是因为
 * 在编译时编译器不能推断出T的类型,导致编译失败.所以将模板类型列表设置为template <int = 0> (Nontype
 * Parameters) 此处int并无实际意义
 */
template <int = 0>
inline bool Unmarshal(int& obj,const Json::Value &root){
    if(!root.isIntegral())
        return false;
    obj = root.asInt();
    return true;
}

template <int = 0>
inline bool Unmarshal(long& obj,const Json::Value &root)

.....

3、匹配stl容器/其他第三方类库的Unmarshal模板

//只能匹配 vector<T> map<string,T> map<long,T> map<int,T> 的一组模板
//vector
template <typename T>
bool Unmarshal(vector<T>& obj,const Json::Value& root){
    if(!root.isArray())
        return false;
    obj.clear();
    bool ret = true;
    for(int i=0;i<root.size();++i){
        T tmp;  //类型T要含有T()构造函数
        if(!Unmarshal(tmp,root[i])) //递归调用Unmarshal函数
            ret = false;
        obj.push_back(tmp);
    }
    return ret;
}

//map key:string
template <typename T>
bool Unmarshal(map<string,T>& obj,const Json::Value& root){
   ...
}

//map key:long
template <typename T>
bool Unmarshal(map<long,T>& obj,const Json::Value& root){
   ...
}

//map key:int
template <typename T>
bool Unmarshal(map<int,T>& obj,const Json::Value& root){
   ...
}

4、匹配自定义struct/class的Unmarshal模板

实现一组只能匹配自己定义的struct/class 就需要我们定义的对象有一些特殊的标志,才能被模板函数识别。在这里选择给我们自己定义的类都实现public的unmarshal方法(实现方式后面讲),这样当编译时发现一个对象含有 publicunmarshal方法时,就知道使是我们自己定义的类,然后就调用特定的模板函数,这里用到到了一个C++的语法 SFINAE(Substitution Failure Is Not An Error) 和std库中enable_if

我们先来看一下C++ std库中 enable_if 的简要实现:

// 版本1 一个空的enable_if 结构体
template <bool, class _Tp = void>
struct enable_if {};

// 版本2 是版本1第一个参数为true的特例化实现,这个版本的enable_if含有 type类型
template <class _Tp>
struct enable_if<true, _Tp> {typedef _Tp type;};

int main(){
    enable_if<true,int>::type a = 3; //匹配版本2,相当于 int a = 3
    enable_if<false,int>::type b = 3; //匹配版本1,enable_if{}没有type类型,触发编译错误
}

SFINAE 准则就是匹配失败并不是错误,如果编译器在匹配一个模板时引发了错误,这时候编译器应当尝试下一个匹配,而不应该报错中止。利用这条规则和enable_if,解析我们自己struct/class Umarshal模板设计如下:

// 检测一个类 是否含有非静态非重载的unmarshal方法
template<typename T>
struct TestUnmarshalFunc {

    //版本1
    template<typename TT>
    static char func(decltype(&TT::unmarshal));

    //版本2
    template<typename TT>
    static int func(...);

    /*
     * 如果类型T没有unmarshal方法,func<T>(NULL)匹配版本1时会产生错误,由于SFINAE准则,只能匹配版本2
     * 的func,此时返回值4个字节,has变量为false.反之 has变量为true
     */
    const static bool has = (sizeof(func<T>(NULL)) == sizeof(char));
};

//如果对象自身含有 unmarshal 方法,则调用对象的unmarshal.否则会因SFINAE准则跳过这个版本的Unamrshal
template <typename T,typename enable_if<TestUnmarshalFunc<T>::has,int>::type = 0>
inline bool Unmarshal(T& obj,const Json::Value &root){
    return obj.unmarshal(root);
}

好了,至此我们对三种基本类型的Umarshal函数设计好了,这时候任意一个T类型 在调用Unmarshal时,最终会与上面三种其中一个匹配。json 为string的可以利用上面的Unmarshal再封装一个版本:

template <typename T>
bool Unmarshal(T& obj,const string &s){
    Json::Reader reader;
    Json::Value root;
    if(!reader.parse(s,root))
        return false;
    return Unmarshal(obj,root);
}

接下来我们看如何在自定义的类中实现unmarshal函数:

//假设有一个People对象,有3个field需要反序列化,根据上面的要求,可能需要我们自己编写unmarshal如下
struct People{
    bool sex;
    int age;
    string name;

    //尽力解析每个field,只有全部正确解析才返回true
    bool unmarshal(const Json::Value &root){
        bool ret = true;
        if(!Json::Unmarshal(sex,root["sex"])){
            ret = false;
        }
        if(!Json::Unmarshal(age,root["age"])){
            ret = false;
        }
        if(!Json::Unmarshal(name,root["name"])){
            ret = false;
        }
        return ret;
    }
};

显然如果field数量很多,就很麻烦,而且解析每个field时,代码格式非常相似,是否存在一个宏可以自动生成呢?答案是肯定的。talk is cheap,show me the code ,上代码!

struct People{
    bool sex;
    int age;
    string name;

    //用JSON_HELP宏把需要序列化的field传进去,就自动在类里生成unmarshal、marshal函数
    JSON_HELP(sex,age,name)
};

// JSON_HELP 是一个变参宏
#define JSON_HELP(...)          \
    UNMARSHAL_OBJ(__VA_ARGS__)  \    //这里生成unmarshal函数
    MARSHAL_OBJ(__VA_ARGS__)

/*
 * UNMARSHAL_OBJ中FOR_EACH宏第一个参数传入一个函数,第二个参数传入一个list
 * 作用是对list中每个元素调用传入的函数,这里有点像python里高阶函数map()的味道
 * 这样就批量生成了下面的代码结构:
 *      if(!Json::Unmarshal(field_1,root["field_1"])){
 *           ret = false;
 *       }
 *      if(!Json::Unmarshal(field_2,root["field_2"])){
 *          ret = false;
 *      }
 *      ... ..
 */
#define UNMARSHAL_OBJ(...)                                  \
    bool unmarshal(const Json::Value& root){                \
        bool ret = true;                                    \
        FOR_EACH(__unmarshal_obj_each_field__,__VA_ARGS__)  \
        return ret;                                         \
    }

#define __unmarshal_obj_each_field__(field)         \
        if(!Json::Unmarshal(field,root[#field])){   \
            ret = false;                            \
        }

//###### FOR_EACH 实现#######
//作用:传入一个函数func和一个list,把func用在list的每个元素上
#define FOR_EACH(func,...) \
    MACRO_CAT(__func_,COUNT(__VA_ARGS__))(func,__VA_ARGS__)

/*
 * FOR_EACH在实现中 COUNT宏用于统计参数个数,返回一个数字,MACRO_CAT宏用于把两个token连接起来,
 * 如果__VA_ARGS__有三个变量为a,b,c 那么这一步宏展开后为:
 * __func_3(func,a,b,c), 而__func_3 __func_2 __func_1 ... 定义如下
 * /

// 宏展开实现伪循环
/*
 * __func_3(func,a,b,c) 具体展开过程:
 * 第一次: __func_1(func,a) __func_2(func,b,c)
 * 第二次: func(a)          __func_1(func,b)    __func_1(func,c)
 * 第三次: func(a)          func(b)             func(c)
 * 最终在a,b,c上都调用了一次传入的func函数
 */
#define __func_1(func,member)     func(member);
#define __func_2(func,member,...) __func_1(func,member)  __func_1(func,__VA_ARGS__)
#define __func_3(func,member,...) __func_1(func,member)  __func_2(func,__VA_ARGS__)
#define __func_4(func,member,...) __func_1(func,member)  __func_3(func,__VA_ARGS__)
#define __func_5(func,member,...) __func_1(func,member)  __func_4(func,__VA_ARGS__)
... ...

//###### COUNT 宏实现#######
//作用: 返回传入参数个数. eg: COUNT(a,b,c)返回3
#define COUNT(...) __count__(0, ##__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define __count__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N

###### MACRO_CAT 宏实现#######
//作用: 将两个token(可以是宏),连接在一起
#define MACRO_CAT(a,b)  __macro_cat__(a,b)
#define __macro_cat__(a,b)  a##b

5、测试

我们的Umarshal Marshal函数编写好了,现在测试一下吧:

场景:有一个map对象存着教师的信息,每个教师又保存着ta教学生信息,数据结构定义如下:

struct Student {
    long    id;
    bool    sex;
    double  score;
    string  name;

    JSON_HELP(id,sex,score,name)
};
struct Teacher {
    string          name;
    int             subject;
    vector<Student> stus;

    JSON_HELP(name,subject,stus)
};

map<string,Teacher> tchs;  //需要序列化和反序列化的对象

测试代码:

// 对应于结构 map<string,Teacher> 的json
string ori = R"(
{
  "Tea_1": {
    "name": "Tea_1",
    "subject": 3,
    "stus": [
      {
        "id": 201721020126,
        "sex": false,
        "score": 80,
        "name": "Stu.a"
      },
      {
        "id": 201101101537,
        "sex": true,
        "score": 0,
        "name": "Stu.b"
      }
    ]
  },
  "Tea_2": {
    "name": "Tea_2",
    "subject": 1,
    "stus": [
      {
        "id": 201521020128,
        "sex": true,
        "score": 59,
        "name": "Stu.c"
      }
    ]
  }
}
)";

int main() {
    map<string,Teacher> tchs;

    // 从json字符串反序列化对象
    bool ret = Json::Unmarshal(tchs,ori);
    if(!ret){
        cout<<"反序列失败"<<endl;
        return 0;
    }else{
        cout<<"反序列成功"<<endl;
    }

    // 序列化对象到 json字符串
    cout<<"输出对象序列化的json:"<<endl;
    string obj2json;
    Json::Marshal(tchs,obj2json);
    cout<<obj2json;
}

//##### 输出结果#####
反序列成功
输出对象序列化的json:
{"Tea_1":{"name":"Tea_1","stus":[{"id":201721020126,"name":"Stu.a","score":80.0,"sex":false},{

到此这篇关于C++ 轻量级对象JSON序列化实现详情的文章就介绍到这了,更多相关C++ 轻量级对象JSON序列化实现内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++操作json文件以及jsoncpp配置详解

    前言 json文件是比较轻量级的文件,格式简单,使用方便.用来存放信息相比其他方式有自己得天独厚的优势. 今天给大家分享的是如何利用C++来操作json文件. 如果你知道如何使用jsoncpp类库,可以不用看附,如果第一次使用,请先到最后,将环境配置好,再进行操作. 一.json文件简介 1.json文件 JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式.它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完

  • C++类结构体与json相互转换

    目录 1. 背景与需求 2. 最终使用的样例代码 3. 实现方法 3.1 基础类型的转换 3.2 类成员注册 3.3 自定义类的转换 3.4 外部调用接口 1. 背景与需求 之前写C#的时候,解析json字符串一般使用的是开源的类库Newtonsoft.Json,方法十分简洁,比如: class Project { public string Input { get; set; } public string Output { get; set; } } JavaScriptSerializer

  • 浅析C/C++,Java,PHP,JavaScript,Json数组、对象赋值时最后一个元素后面是否可以带逗号

    1 C,C++,Java,PHP都能容忍末尾的逗号 C,C++,Java中对数组赋值时,最后一个元素末尾的逗号可有可无.下面两行代码对这些语言来说是等效的. int a[] = {1,2,3}; /* 正确 */ int a[] = {1,2,3,}; /* 正确 */ PHP这一点也继承了C的特点,下面的两行代码等效. $a = array(1,2,3); /* 正确 */ $a = array(1,2,3,); /* 正确 */ 2 JavaScript视末尾逗号为语法错误! 然而到了Jav

  • C++使用jsoncpp解析json的方法示例

    前言: 曾经一段时间XML成为互联网业界内的数据传输格式标准,但有人对XML提出了质疑,认为XML数据格式比较繁杂,冗长等,于是提出了一种新的表示格式-JSON. 对于JSON格式,在此就不作详细的说明了,下面主要讨论下C++解析json文件的工具-Jsoncpp的使用. JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,和xml类似,本文主要对VS2008中使用Jsoncpp解析json的方法做一下记录. Jsoncpp是个跨平台的开源库,下载地址:

  • Linux系统下如何使用C++解析json文件详解

    1. 背景 工作需要,下班回来自己造轮子,记录以后查阅. JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,和xml类似,本文主要Linux下使用Jsoncpp解析json的方法做一下记录. 2. 关于jsoncpp库的使用简介 使用jsoncpp有两种方法 方法一:使用Jsoncpp生成的lib文件 解压上面下载的Jsoncpp文件,在jsoncpp默认生成静态链接库. 在工程中引用,只需要包含include/json下的头文件及生成的.lib文件即

  • C/C++中CJSON的使用(创建与解析JSON数据)

    目录 一.cJSON介绍 二.JSON简介.语法介绍 2.1 JSON是什么? 2.2 JSON语法介绍 三.cJSON创建简单JSON数据并解析 3.1 新建工程 3.2 创建JSON数据 3.3 解析JSON数据 四.cJSON创建嵌套的对象数据 4.1 创建json数据 4.2 解析JSON数据 五.cJSON带数组的JSON数据 5.1 创建json数据 5.2 解析JSON数据 一.cJSON介绍 cJSON 是一个超轻巧,携带方便,单文件,可以作为 ANSI-C 标准的 JSON 解

  • C++使用jsoncpp库解析Json

    前言 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,和xml类似,本文主要对VS2008中使用Jsoncpp解析json的方法做一下记录. Jsoncpp是个跨平台的开源库,下载地址:http://sourceforge.net/projects/jsoncpp/. 方法一:使用Jsoncpp生成的lib文件 解压上面下载的Jsoncpp文件,在jsoncpp-src-0.5.0/makefiles/vs71目录里找到jsoncpp.sln,用VS

  • C++ 轻量级对象JSON序列化实现详情

    目录 1.设计思路 2.匹配基本类型的Unmarshal模板 3.匹配stl容器/其他第三方类库的Unmarshal模板 4.匹配自定义struct/class的Unmarshal模板 5.测试 背景: 在项目里经常遇到对象和json字符串的相互转换这类问题,在大多数程序里,一般这个问题都比较有比较好的解决方法,往往一个函数搞定.但是到了c++这边却需要我们手撸json库一个一个字段初始化/序列化.如果这时候有一个函数 可以一行代码 unmarshal /marshal 对象岂不是很方便?本文以

  • Django 再谈一谈json序列化

    我们知道JSON字符串是目前流行的数据交换格式,在pyhton中我们通过json模块,将常用的数据类型转化为json字符串.但是,json支持转化的数据类型是有限的. 比如,我们通过ORM从数据库查询出的结果,试图通过json序列化: from .models import UserInfo def index(request): user_list = UserInfo.objects.all() import json return HttpResponse(json.dumps(user_

  • C#对JSON与对象的序列化与反序列化

    一.利用Web服务中的JavaScriptSerializer 类 System.Web.Script.Serialization空间,位于System.Web.extensions.dll中. JavaScriptSerializer jss = new JavaScriptSerializer(); Console.WriteLine(jss.MaxJsonLength); //默认接受最大的长度是 2097152 这个是接受JSON字符串的最大长度,超长会有什么后果呢?试下 Person

  • C#使用Json.Net对JSON与对象的序列化与反序列化

    目录 一.使用Json.Net 1.把DataTable转换成json格式,使用最新Json.Net DLL ,已经内置转换器. 2.对Json.Net序列化和反序列化的控制 二.空值的处理 三.默认值的处理 四.忽略某些属性 五.支持非公共成员 六.日期处理 1.使用IsoDateTimeConverter 和 JavaScriptDateTimeConverter标准格式 1.如果你要序列化的日期格式是统一的,可以考虑如下方式 2.如果想要不同的日期类型成员序列化后,以不同的形式显示. 2.

  • 基于Json序列化和反序列化通用的封装完整代码

    1. 最近项目已经上线了 ,闲暇了几天 想将JSON的序列化以及反序列化进行重新的封装一下本人定义为JSONHelp,虽然Microsoft 已经做的很好了.但是我想封装一套为自己开发的项目使用.方便后期的扩展以及开发使用. 2. 什么是 JSON ? JSON:JavaScript 对象表示法(JavaScript Object Notation).JSON 是存储和交换文本信息的语法.类似 XML.JSON 比 XML 更小.更快,更易解析.  现在开发Web应用程序 JSON 是 必不可少

  • Go语言基础Json序列化反序列化及文件读写示例详解

    目录 概述 JSON序列化 结构体转JSON map转JSON 切片转JSON JSON反序列化 JSON转map JSON转结构体 JSON转切片 写JSON文件 map写入JSON文件 切片写入JSON文件 结构体写入JSON文件 读JSON文件 解码JSON文件为map 解码JSON文件为切片 解码JSON文件为结构体 示例 概述 JSON(JavaScript Object Notation,JavaScript对象表示法)是一种轻量级的.键值对的数据交换格式.结构由大括号'{}',中括

  • Json序列化和反序列化方法解析

    复制代码 代码如下: /// <summary>        /// Json序列化,用于发送到客户端        /// </summary>        public static string ToJsJson(this object item)        { DataContractJsonSerializer serializer = new DataContractJsonSerializer(item.GetType()); using (MemoryStr

  • JSON序列化与解析原生JS方法且IE6和chrome测试通过

    复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv=&qu

  • asp.net JSON 序列化技术分析

    在ASP.NET 3.5开始,.NET平台下共支持两种序列化方式: 复制代码 代码如下: 1.DataContractSerializer 2.JavascriptSerializer 前者主要是根据数据契约(DataContract)进行序列化,通过如下定义的数据契约: 代码 复制代码 代码如下: <DataMember()> _ Public Class Model <DataMember()> Public Property PropertyA As String <D

  • C#中 Json 序列化去掉null值的方法

    要将一个对象序列化,可是如果对象的属性为null的时候,我们想将属性为null的都去掉. 在这里我使用Newtonsoft.Json.dll 记录一下序列化以及反序列化 json字符串转对象 Model model=JsonConvert.DeserializeObject<Model>(val); 将对象转化为json格式字符串 string jsonString = JsonConvert.SerializeObject(obj); 那么如何序列化为json时过滤掉NULL呢?? var

随机推荐