序列化与反序列化

序列化和反序列化概念

序列化是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。简单来说,就是把内存中的数据对象按照一定的规则变成一个线性的字节序列 ,这样就方便存储到文件、数据库或者在网络上传输。

在网络通信中,数据并不是简单的把数据"揉合"在一起,然后再发送出去。而是将数据按照一定的方式组合起来,才能让接收方进行合理的解析。假如有以下数据。

新年快乐2025.1.10Jack

按照我们人类的思考方式,这个数据是叫一个Jack的用户在2025.1.10发送了一个消息,叫新年快乐,这是很容易理解的。但是对于计算机来说,这段数据可能是叫新年快乐的用户在2025.1.10发送了Jack的消息,或者是其他形式的消息组合。所以对于计算机来说,这段数据之间应该如何进行区分,分节符在哪里?

对于上面的问题,我们要设定一些规则来组织数据,这种行为被称为序列化。而从组织好的数据中还原出目标信息,称为反序列化。反序列化是序列化的逆过程,它将序列化后的字节流、XML、JSON 等格式的数据重新转换为原来的数据结构或对象的过程。这样就可以在程序中再次使用这些数据,就好像它们从未离开过内存一样。

当前主流的序列化方式有三种,分别是是XML,JSON,protobuf。接下来我们将重点学习JSON的序列化方式,如果想用二进制的方式来序列化数据,可以使用protobuf,不过学习起来会比较难。我会在后面进行protobuf的讲解。

jsoncpp的使用

jsoncpp库中的类被定义到了一个Json命名空间中,建议在使用这个库的时候先声明这个命名空间。

cpp 复制代码
using namespace Json;

使用jsoncpp库解析json格式的数据,我们只需要掌握三个类:

  • Value类:将json支持的数据类型进行了包装,最终得到一个Value类型。
  • FastWriter类:将Value对象中的数据序列化为字符串。
  • Reader类:反序列化,将json字符串解析成Value类型。

Value类

这个类可以看做是一个包装器,它可以封装Json支持的所有类型,这样我们在处理数据的时候就方便多了。

枚举类型 说明 翻译
nullValue 'null value' 不表示任何数据,空值
intValue signed integer value 表示有符号整数
uintValue usigned integer value 表示无符号整数
realValue double value 表示浮点数
stringValue UTF-8 string value 表示utf8格式的字符串
booleanValue bool value 表示布尔数
arrayValue array value(ordered list) 表示数组,即Json串中的[]
objectValue object value(collection of name/value pairs) 表示键值对,即Json串中的{}
构造函数

Value类为我们提供了很多构造函数,通过构造函数来封装数据,最终得到一个统一的类型。

cpp 复制代码
Value(ValueType type = nullValue);
Value(Int value);
Value(UInt value);
Value(Int64 value);
Value(double value);
Value(const char* value);
Value(const char* begin, const char* end);
Value(bool value);
Value(const Value& other);
Value(Value&& other);
检测保存的数据类型
cpp 复制代码
bool isNull() const;
bool isBool() const;
bool isInt() const;
bool isInt64() const;
bool isUInt() const;
bool isUInt64() const;
bool isIntegral() const;
bool isDouble() const;
bool isNumberic() const;
bool isString() const;
bool isArray() const;
bool isObject() const;

其中这里有两个接口比较特殊,一个是 isNumeric() 另一个是 isIntegral()

先对 isNumeric() 进行说明,字面意思就是"是否为数字",实际上在 Json::Value 类的实现中等同于 isDouble(),因此这两个函数是等效的,实现代码如下:

cpp 复制代码
bool Value::isNumeric() const {
   return isDouble(); }

在使用过程中可以直接用 isDouble() 代替 isNumeric(),反之亦然。
isIntegral() 的作用主要是对浮点型数据的值进行了严格限制,如果类型为 int 或者 uint,该接口直接返回真值。如果类型为 double,因为 m a x U I n t 64 = 2 64 − 1 maxUInt64=2^{64}-1 maxUInt64=264−1 不能精确表示为 double,因此 double(maxUInt64)将四舍五入为 2 64 2^{64} 264。因此,我们要求该值严格小于限制。

将Value对象转换为实际类型
cpp 复制代码
Int asInt() const;
UInt asUInt() const;
Int64 asInt64() const;
UInt64 asUInt64() const;
LargestInt asLargestInt() const;
LargestUInt asLargestUInt() const;
JSONCPP_STRING asString() const;  //返回C++风格的字符串
float asFloat() const;
double asDouble() const;
bool asBool() const;
const char* asCString() const;  //返回C分格的字符串
对json数组的操作
cpp 复制代码
ArrayIndex size() const;
Value& operator[](ArrayIndex index);
Value& operator[](int index);
const Value& operator[](ArrayIndex index) const;
const Value& operator[](int index) const;
// 根据下标的index返回这个位置的value值
// 如果没找到这个index对应的value,这回第二个参数的defaultValue
Value get(ArrayIndex index,const Value& defaultValue) const;
Value& append(const Value& value);
const_iterator begin() const;
const_iterator end() const;
iterator begin() const;
iterator end();
对json对象的操作
cpp 复制代码
Value& operator[](const char* key);
const Value& opertaor[](const char* key) const;
Value& operator[](const JSONCPP_STRING key);
const Value& operator[](const JSON_STRING& key) const;
Value& operator[](const StaticString& key);

// 通过key,得到value的值
Value get(const char* key, const Value& defaultValue) const;
Value get(const JSONCPP_STRING& key, const Value& defaultValue) const;
Value get(const CppTL::ConstString& key, const Value& defaultValue) const;

// 得到对象中所有的键值
typedef std::vector<std::string> Members;
Members getMemberNames() const;
将Value对象数据序列化为string
cpp 复制代码
// 序列化得到的字符串有样式 -> 带换行 -> 方便阅读
// 写配置文件的时候
std::string toStyledString() const;

FastWriter类

cpp 复制代码
// 将数据序列化 -> 换行
// 进行数据的网络传输
std::string Json::FastWriter::write(const Value& root);

Reader类

当需要进行反序列化的时候,就需要Reader类,核心函数接口如下:
bool Json::Reader::parse(const string& document, Value& root, bool collectComments = true);

参数:

  • document:json格式字符串
  • root:传出参数,存储了json字符串中解析出的数据
  • collectComments:是否保存json字符串中的注释信息

bool Json::Reader::parse(const char* beginDoc, const char* endDoc, Value& root, bool colletcComments = true);

通过begindocenddoc指针定位一个json字符串,这个字符串可以是完成的json字符串,也可以是部分json字符串。


bool parse(istream& is, Value& root, bool collectComments = true);
write的文件流(ostream),read的文件流(istream)。假设要解析的json数据在磁盘文件中,is流对象指向一个磁盘文件,读操作

举例

序列化

假设我们要将下面的数据进行序列化:

复制代码
name = Jack;
age = 12;
message = hello world;
time = 2025.1.10

代码如下:

cpp 复制代码
#include <iostream>
#include <jsoncpp/json/json.h>
  
using namespace std;

int main()
{
    // 创建一个Json::Value对象
    Json::Value root;

    // 向对象中添加数据
    root["name"] = "Jack";
    root["age"] = 22;
    root["messmge"] = "hello world";
    root["time"] = "2025.1.10";

    // 创建一个Json::FastWriter
    Json::FastWriter w;
  
    // 创建一个Json::StyledWriter
    Json::StyledWriter w1;
  
    // 将Json::Value对象转换为字符串
    string output = w.write(root);
    string res = w1.write(root);
  
    // 打印输出
    cout << "FastWriter格式:" << endl;
    cout << output << endl;

    cout << "StyledWriter格式:" << endl;
    cout << res << endl;
    return 0;
}

我们可以发现顺序是按照字母顺序进行排列的。Fastwriter类和StyledWriter类的序列化形式的数据内容是一样的,区别在于序列化格式不同。

  • StyledWriter:会给json数据添加换行,让数据看起来更美观。

  • FastWriter:去掉所有的换行,数据之间用逗号隔开。整个json就是一行数据。
    如过我们的数据需要用数组和对象来进行表示,其中用[]是数组,用{}是对象,如下:

    name = Jack;
    age = 12;
    message = hello world;
    time = 2025.1.10
    [
    "School",
    "student"
    ]
    {
    "China":"Beijing",
    "US":"NewYork"
    }

那么代码就可以这样写:

cpp 复制代码
#include <iostream>
#include <jsoncpp/json/json.h>

using namespace std;
int main()
{
    // 创建一个Json::Value对象
    Json::Value root;
  
    // 向对象中添加数据
    root["name"] = "Jack";
    root["age"] = 22;
    root["message"] = "hello world";
    root["time"] = "2025-01-10"; 

    // 创建数组并添加到root中合适的键对应位置
    Json::Value arr;
    arr.append("School");
    arr.append("Student");
    root["tags"] = arr; // 修改添加方式,让数组作为"tags"键对应的值

    // 创建子对象并添加到root中合适的键对应位置
    Json::Value object;
    object["China"] = "Beijing";
    object["US"] = "NewYork";
    root["locations"] = object; // 修改添加方式,让该对象作为"locations"键对应的值
    
    // 创建一个Json::StyledWriter
    Json::StyledWriter w1;
    
    // 将Json::Value对象转为字符串
    string res = w1.write(root);
  
    // 打印输出
    cout << "StyledWriter格式:" << endl;
    cout << res << endl;

    return 0;
}

反序列化

cpp 复制代码
int main()
{

    std::string str = "{\"content\": \"Hello JsonCpp\"}";
    Json::Reader reader;
    Json::Value root;
    if (reader.parse(str, root))
        std::cout << root["content"].asString() << std::endl;
    return 0;
}

调用 Reader.parse() 接口尝试解析 json 字符串 str,当 str 满足 json 格式之后,调用 Value[] 操作符将 "content" 的值取出来,然后再进行类型转换,取出实际的类型数据。


cpp 复制代码
int main()
{
    Json::Value part1;
    part1["haha"] = "haha";
    part1["hehe"] = "hehe";
    
    Json::Value root;
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["desc"] = "this is a + oper";
    root["test"] = part1;
    //Json::FastWriter w;
    Json::StyledWriter w;
    std::string res = w.write(root);
    std::cout << res << std::endl;
    
    sleep(3);
    
    Json::Value v;
    Json::Reader r;
    r.parse(res, v);
    int x = v["x"].asInt();  // 类型转换
    int y = v["y"].asInt();
    char op = v["op"].asInt();
    std::string desc = v["desc"].asString();
    Json::Value temp = v["test"];
    std::cout << x << std::endl;
    std::cout << y << std::endl;
    std::cout << op << std::endl;
    std::cout << desc << std::endl;
    
    return 0;
}
相关推荐
李天琦4 分钟前
git查看commit属于那个tag
linux·git·云计算
liulilittle7 分钟前
关于DDOS
linux·运维·服务器·网络·ddos·通信
LetsonH1 小时前
Ubuntu 22.04 系统下 Docker 安装与配置全指南
linux·ubuntu·docker
pianmian13 小时前
3D Tiles高级样式设置与条件渲染(3)
linux·服务器·前端
清晨朝暮3 小时前
【Linux 学习计划】-- 命令行参数 | 环境变量
linux·运维·学习
聂 可 以3 小时前
Nginx基础篇(Nginx目录结构分析、Nginx的启用方式和停止方式、Nginx配置文件nginx.conf文件的结构、Nginx基础配置实战)
linux·运维·nginx
Clownseven4 小时前
用Git管理你的服务器配置文件与自动化脚本:版本控制、变更追溯、团队协作与安全回滚的运维之道
运维·服务器·git
Joker 0074 小时前
Ubuntu 安装 FSL 及多模态脑MRI的去颅骨处理(含 HD-BET 深度学习方法)
linux·深度学习·ubuntu
Roki Zhang4 小时前
RustDesk 搭建自建服务器并设置服务自启动
运维·服务器
代码讲故事4 小时前
解决 xmlsec.InternalError: (-1, ‘lxml & xmlsec libxml2 library version mismatch‘)
linux·python·pip·lxml·xmlsec·libxml2