【C++】一文搞懂JSON序列化和反序列(让你有一种相见恨晚的感觉!!)

发布于:2024-08-10 ⋅ 阅读:(116) ⋅ 点赞:(0)

目录

一、前言

二、深度解析序列化和反序列化 

🔥引入序列化和反序列化🔥 

🔥什么是序列化(Serialization)?🔥 

🔥什么是反序列化(Serialization)?🔥  

🔥 序列化和反序列化的应用场景🔥

🔥常见的序列化格式🔥 

三、JSON的介绍与使用

💧JSON的数据格式💧 

💧Jsoncpp库的介绍💧  

①:Json::Value 类   -----  中间数据存储类

②:Json::StreamWriter 类   -----  用于数据序列化 

③:Json::CharReader  类   -----  用于数据反序列化  

💧Json实战应用💧 

🥝Json序列化 

🍇Json反序列化 

四、总结

五、共勉 


一、前言

        在现代软件开发中,JSON(JavaScript Object Notation)已经成为数据交换的标准格式之一。无论是在前后端通信API 数据传输,还是在配置文件管理中,JSON 的简洁性和可读性使其广泛应用。然而,处理 JSON 数据的关键在于序列化反序列化——将数据结构转换为 JSON 格式,以及将 JSON 格式解析回数据结构。这篇博客将深入探讨 JSON 序列化和反序列化的概念,并通过实例展示如何在 C++ 中高效地处理 JSON 数据。

二、深度解析序列化和反序列化 

🔥引入序列化和反序列化🔥 

 生活中的类比:打包和解包

  • 假设你要寄送一个复杂的玩具给朋友,但这个玩具有很多零件和组件,直接寄送会很麻烦,容易丢失零件,也不方便运输。为了方便,你决定把玩具拆解开来,打包成一个扁平的小盒子,方便运输。

 序列化

  • 你把玩具拆解开来,把所有的零件和组件按照一定的顺序放入一个盒子里,并标记好每个零件的位置。这相当于把复杂的对象转化为一个易于存储和传输的格式

反序列化: 

  • 朋友收到包裹后,根据你的标记把零件重新组装成原来的玩具。这相当于将线性数据转换回原本的对象。 

🔥什么是序列化(Serialization)?🔥 

序列化是将 数据结构对象 转换成一种可以存储或传输的格式的过程。简单来说,就是把复杂的数据变成一个“线性”的形式(如字符串、二进制数据),这样就可以方便地存储到文件、数据库,或通过网络传输。 

🔥什么是反序列化(Serialization)?🔥  

反序列化是序列化的逆过程,它将存储或传输的数据格式转换回原本的数据结构或对象。也就是说,通过反序列化,可以从存储或传输的“线性”数据中恢复出原本的对象或数据结构。 


🔥 序列化和反序列化的应用场景🔥

数据存储

  • 在数据库中存储对象时,可以先将对象序列化成一个可以存储的格式(如JSON或二进制),存储后再需要时通过反序列化恢复成对象。

网络传输(常用)

  • 在网络通信中,常常需要将对象序列化为字符串或二进制数据,通过网络传输到另一端,然后通过反序列化恢复成对象。

文件存储: 

  • 例如保存游戏状态时,可以将游戏对象序列化成文件,之后读取文件时再反序列化恢复游戏状态。 

🔥常见的序列化格式🔥 

 JSON(JavaScript Object Notation)通常存储在 Jsoncpp库中

  • 一种基于文本的轻量级数据交换格式,易于阅读和编写。

 XML(eXtensible Markup Language)

  • 也是一种基于文本的数据交换格式,广泛用于配置文件和数据交换。 

二进制格式: 

  • 某些应用程序会使用二进制格式进行序列化,因为它更紧凑,效率更高。 

三、JSON的介绍与使用

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,广泛应用于数据存储和 网络通信中。为了在C++中处理JSON数据,通常使用一些第三方库,如 JsonCpp 库。  

💧JSON的数据格式💧 

Json 是⼀种数据交换格式,它采⽤完全独⽴于编程语⾔的⽂本格式来存储和表⽰数据。

例如 : 我们想表⽰⼀个同学的学⽣信息 

  • C语言代码表示: 
char* name = "xax";
int age = 18;
float score[3] = {88.5 , 99 , 58};
  •  Json代码表示: 
{
    "姓名" :"xas",
    "年龄" : "18",
    "成绩" : "[88.5 , 99 , 58]",
    "爱好" : 
            {
                 "书籍" : "我与地坛",
                 "运动" : "打乒乓球"
            }
}

从上面的代码可以发现 Json 的数据类型包括 对象数组字符串数字等。

  • 对象 :使用花括号 { } 括起来的表示一个对象
  • 数组 :使用中括号 [ ] 括起来的表示一个数组
  • 字符串 :使用常规双引号 " " 括起来的表示一个字符串
  • 数字 :包括整形和浮点型,直接使用

同时我们还可以发现一些 Json的语法规则 

键值对格式: { key : value}

  • JSON 是由一组键值对组成的,每个键值对都以 "键": "值" 的形式表示。
  • 键和值之间用冒号 : 分隔。

键(key)必须是字符串 :

  • JSON 的键必须是用双引号 "" 包围的字符串。在示例中,"姓名", "年龄", "成绩""爱好" 都是字符串形式的键。 

值(value)可以是多种类型 :

  • 字符串:例如 "xas", "我与地坛"
  • 数字:例如 18, 88.5, 99, 58(尽管在示例中这些数字被当作字符串)。
  • 布尔值:例如 true, false(在此例中未使用)。
  • 数组:例如 "[88.5, 99, 58]",表示一个由多个值组成的有序列表。
  • 对象:例如 "爱好" 对应的值 { "书籍": "我与地坛", "运动": "打乒乓球" },表示另一个嵌套的键值对集合。
  • null:表示空值(在此例中未使用)。

嵌套结构 

  • JSON 可以包含嵌套的对象,即对象内的键值对中,值可以是另一个对象。例如,"爱好" 对应的值是一个嵌套的 JSON 对象。 

逗号分隔 

  • 在对象中,键值对之间用逗号 , 分隔。在数组中,元素之间也用逗号 , 分隔。 

💧Jsoncpp库的介绍💧  

在 C++ 中处理 JSON 数据时,需要了解 JSON 对象(Json::Value以及相关的函数非常重要。我会以  JsonCpp 这个库为例,详细讲解它们中的常用函数和对象,以及如何使用。

  • 在使用Jsoncpp这个库之前,我们需要了解 三个必须要掌握的类 

①:Json::Value 类   -----  中间数据存储类

  • 如果要将数据对象进行序列化,就需要先存储到 Json::Value对象中,组织数据与数据之间的关系
  • 如果要将数据对象进行反序列化,就是将数据解析后,将数据的对象放入到 Json::Value

创建和初始化 

Json::Value jsonObj;  // 创建一个空的 JSON 对象

// 直接赋值初始化
jsonObj["name"] = "Alice";
jsonObj["age"] = 30;
jsonObj["is_student"] = false;

// 数组初始化
Json::Value grades(Json::arrayValue);  // 创建一个 JSON 数组
grades.append(85);
grades.append(90);
grades.append(92);
jsonObj["grades"] = grades;

访问数据 

std::string name = jsonObj["name"].asString();  // 获取字符串
int age = jsonObj["age"].asInt();               // 获取整数
bool isStudent = jsonObj["is_student"].asBool();  // 获取布尔值

修改数据 

jsonObj["age"] = 31;  // 修改键对应的值
jsonObj["grades"].append(95);  // 向数组中添加新元素

删除键值对 

jsonObj.removeMember("is_student");  // 删除键为 "is_student" 的键值对

遍历对象 

for (Json::Value::const_iterator it = jsonObj.begin(); it != jsonObj.end(); ++it) 
{
    std::cout << it.key().asString() << " : " << *it << std::endl;
}

②:Json::StreamWriter 类   -----  用于数据序列化 

Json::StreamWriter 用于将 JSON 对象序列化(转换为文本形式)并写入到输出流(例如文件流或标准输出)。

class JSON_API StreamWriter 
{
  virtual int write(Value const& root, std::ostream* sout) = 0;
}

 class JSON_API StreamWriterBuilder : public StreamWriter::Factory 
{
  virtual StreamWriter* newStreamWriter() const;
}

 StreamWriter::write

  • 功能:将 Json::Value 对象写入指定的输出流。
  • 参数
    • const Json::Value &root要写入的 JSON 对象。
    • std::ostream *sout输出流指针,表示数据将写入的目标位置。
  • 返回值:返回一个 int,通常表示写入的数据量。
Json::Value root;
root["name"] = "Alice";
root["age"] = 25;

Json::StreamWriterBuilder writer;
std::unique_ptr<Json::StreamWriter> jsonWriter(writer.newStreamWriter());
jsonWriter->write(root, &std::cout);
std::cout << std::endl;

 StreamWriterBuilder

  • 功能:构建 StreamWriter 对象的辅助类。提供了一些选项,可以自定义输出格式(如缩进和格式化)。

③:Json::CharReader  类   -----  用于数据反序列化  

 Json::CharReader 用于从输入流中读取 JSON 数据并将其解析为 Json::Value 对象。

 class JSON_API CharReader 
{
     virtual bool parse(char const* beginDoc, char const* endDoc, 
     Value* root, std::string* errs) = 0;
}

 class JSON_API CharReaderBuilder : public CharReader::Factory 
{
     virtual CharReader* newCharReader() const;
}

 CharReader::parse

  • 功能:解析 JSON 字符串并将其转换为 Json::Value 对象。
  • 参数
    • const char *beginDoc指向要解析的 JSON 文档的起始位置。
    • const char *endDoc指向 JSON 文档的结束位置(通常是 beginDoc + length)。
    • Json::Value *root指向将保存解析结果的 Json::Value 对象。
    • Json::String &errs用于存储任何解析过程中发生的错误信息。
  • 返回值:返回 bool,如果解析成功,返回 true,否则返回 false
std::string rawJson = R"({"name": "Alice", "age": 25})";
Json::CharReaderBuilder reader;
Json::Value root;
std::string errs;

std::unique_ptr<Json::CharReader> jsonReader(reader.newCharReader());
bool parsingSuccessful = jsonReader->parse(rawJson.c_str(), rawJson.c_str() + rawJson.size(), &root, &errs);

if (parsingSuccessful) 
{
    std::cout << root["name"].asString() << std::endl;  // 输出 "Alice"
} else {
    std::cerr << "Failed to parse JSON: " << errs << std::endl;
}

CharReaderBuilder

  • 功能:构建 CharReader 对象的辅助类。通过配置,可以自定义解析器的行为。

💧Json实战应用💧 

在实战应用前,我的介绍一下我们的实验环境:

  •  在 Linux(Ubuntu ) 服务器上进行实验 ,使用C++代码

由于Jsoncpp库是一个第三方库,我们需要自己手动安装

sudo apt-get install -y libjsoncpp-dev
  • 安装好后,进行检查是否安装成功 

🥝Json序列化 

把复杂的对象转化为一个易于存储和传输的格式

#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <jsoncpp/json/json.h>
#include <sstream>

// 实现数据的序列化
bool Hierarch(const Json::Value &val , std::string &body)
{
    std::stringstream ss;
    // 进行序列化
    // 因为 StreamWriter 它的基类是一个抽象类,有纯虚函数存在,不能进行实例化
    // 所以通过工厂类 StreamWriterBuilder 来产生派生类对象
    Json::StreamWriterBuilder swb;
    // 父类对象指向子类对象
    // 注意 sw 是一个 new 出来的一个对象,需要去释放它 --- 采用智能指针
    std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter()); //实例化结束
    int ret = sw->write(val , &ss);
    // 这里序列化成功,会返回一个 0
    if(ret!=0)
    {
        std::cout<<"Json Hierarch failed!\n";
        return false;
    }
    body = ss.str();
    return true;

}


int main()
{
    const char* name  = "xas";
    int age = 18;
    const char* sex = "男";
    float score[3] = {88 , 77.5 , 66};

    // 进行数据传输
    Json::Value student;
    student["姓名"] = name;
    student["年龄"] = age;
    student["性别"] = sex;
    student["成绩"].append(score[0]);
    student["成绩"].append(score[1]);
    student["成绩"].append(score[2]);

    Json::Value fav;
    fav["书籍"] = "我与地坛";
    fav["运动"] = "打乒乓球";
    student["爱好"] = fav;
    std::string body;
    Hierarch(student , body);
    std::cout<<body<<std::endl;
    return 0;
}


🍇Json反序列化 

将线性数据转换回原本的对象。  

#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <jsoncpp/json/json.h>
#include <sstream>

// 实现数据的序列化  --- 将原本的对象,转换为 Json::Value 格式存储
bool Hierarch(const Json::Value &val , std::string &body)
{
    std::stringstream ss;
    // 进行序列化
    // 因为 StreamWriter 它的基类是一个抽象类,有纯虚函数存在,不能进行实例化
    // 所以通过工厂类 StreamWriterBuilder 来产生派生类对象
    Json::StreamWriterBuilder swb;
    // 父类对象指向子类对象
    // 注意 sw 是一个 new 出来的一个对象,需要去释放它 --- 采用智能指针
    std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter()); //实例化结束
    int ret = sw->write(val , &ss);
    // 这里序列化成功,会返回一个 0
    if(ret!=0)
    {
        std::cout<<"Json Hierarch failed!\n";
        return false;
    }
    body = ss.str();
    return true;

}


// 反序列化  -- 将 Json::Value 对象 转换成 原本的数据 
bool UnHierarch( const std::string &body , Json::Value &val)
{
    // 因为 CharReader 它的基类是一个抽象类,有纯虚函数存在,不能进行实例化
    // 所以通过工厂类 CharReaderBuilder 来产生派生类对象
    Json::CharReaderBuilder crb;
    // 父类对象指向子类对象
    // 注意 cr 是一个 new 出来的一个对象,需要去释放它 --- 采用智能指针
    std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
    std::string errs;
    // 进行数据解析
    bool ret = cr->parse(body.c_str() , body.c_str() + body.size() , &val , &errs);
    if(ret == false)
    {
        std::cout<<"Json UnHierarch failed\n";
        return false;
    }
    return true;
}

int main()
{
    const char* name  = "xas";
    int age = 18;
    const char* sex = "男";
    float score[3] = {88 , 77.5 , 66};

    // 进行数据传输
    Json::Value student;
    student["姓名"] = name;
    student["年龄"] = age;
    student["性别"] = sex;
    student["成绩"].append(score[0]);
    student["成绩"].append(score[1]);
    student["成绩"].append(score[2]);

    Json::Value fav;
    fav["书籍"] = "我与地坛";
    fav["运动"] = "打乒乓球";
    student["爱好"] = fav;
    std::string body;
    Hierarch(student , body);
    std::cout<<body<<std::endl;


    std::string str = R"({"姓名" : "WD" , "年龄" : 19 , "成绩" : [32,45.5,56]})";
    Json::Value stu;
    bool ret = UnHierarch(str , stu);
    if(ret == false)
    {
        return -1;  // 退出
    }
    std::cout<<"姓名: "<<stu["姓名"].asString()<<std::endl;
    std::cout<<"年龄: "<<stu["年龄"].asInt()<<std::endl;
    int sz = stu["成绩"].size();
    for(int i = 0;i < sz ; i++)
    {
        std::cout<<"成绩: "<<stu["成绩"][i].asFloat()<<std::endl;
    }

    return 0;
}


四、总结

在 C++ 项目中,使用 JSON 格式进行数据交换是非常常见的。为了实现 JSON 数据的序列化将数据结构转换为 JSON 字符串)和反序列化将 JSON 字符串解析为数据结构),我们可以使用 JsonCpp 库中的 Json::StreamWriterJson::CharReader

序列化

  • 序列化过程将数据结构转换为 JSON 字符串,以便在网络上传输或存储。
  • Json::StreamWriterBuilder 是一个工厂类,它帮助我们创建 StreamWriter 对象用于生成 JSON 字符串。通过智能指针来管理 StreamWriter 对象,可以确保在使用完毕后自动释放资源。

反序列化

  • 反序列化过程则是将 JSON 字符串解析回数据结构。类似地,Json::CharReaderBuilder 用于生成 CharReader 对象来处理解析操作。通过智能指针管理 CharReader 对象,可以避免手动释放内存的麻烦。

在实际应用中,确保数据类型的一致性非常重要。例如,在反序列化时,如果试图将一个字符串转换为整数,程序将抛出异常。因此,在进行类型转换前,检查 JSON 数据的类型是避免错误的关键。

五、共勉 

以下就是我对 【C++】Json序列化和反序列化 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对 【C++11】 的理解,请持续关注我哦!!!     


网站公告

今日签到

点亮在社区的每一天
去签到