1、与Writer模块功能相反,可以将Reader理解成一个反序列化的工具,Writer的作用主要是将Value对象转成string或者流式的结构,Reader的作用主要是将流式的结构转成Value类型的对象。Reader类的主要职责有3个,解析 JSON 字符串:将 JSON 格式的字符串读取并解析成相应的 C++ 数据结构。处理不同的数据类型,支持解析 JSON 对象、数组、字符串、数字、布尔值和 null。处理错误,在解析过程中,如果遇到格式错误或不符合 JSON 标准的情况,Reader 类能够捕获这些错误并提供相关的错误信息。示例代码如下:
#include <json/json.h>
#include <iostream>
#include <string>
int main() {
std::string jsonString = R"({"name": "Alice", "age": 30, "is_student": false})";
Json::CharReaderBuilder readerBuilder;
Json::Value root;
std::istringstream jsonStream(jsonString);
std::string errs;
// 解析 JSON 字符串
if (Json::parseFromStream(readerBuilder, jsonStream, &root, &errs)) {
std::cout << "Name: " << root["name"].asString() << std::endl;
std::cout << "Age: " << root["age"].asInt() << std::endl;
std::cout << "Is Student: " << root["is_student"].asBool() << std::endl;
} else {
std::cerr << "Error parsing JSON: " << errs << std::endl;
}
return 0;
}
下面这个接口是实际开发中经常会用到的接口,当然我可以也可以向StreamWriterBuilder那样设置解析的属性,这个不难,不再进行介绍,后面会详细介绍下实现解析功能的精髓Token的设计。
bool JSON_API parseFromStream(CharReader::Factory const&, IStream&, Value* root,
String* errs);
2、Token是Reader模块中的精髓,它是jsoncpp 库中用于表示 JSON 数据流中各个组成部分的类。在 JSON 数据解析的过程中,Token 代表了 JSON 字符串中的基本元素,如对象的开始、数组的结束、字符串、数字等。通过对这些标记的处理,jsoncpp 能够逐步解析 JSON 数据并将其转换为 C++ 数据结构。Token的定义如下:
enum TokenType {
tokenEndOfStream = 0, //数据流的结束
tokenObjectBegin, //对象的开始 {
tokenObjectEnd, //对象的结束 }
tokenArrayBegin, //数组的开始 [
tokenArrayEnd, //数组的结束 ]
tokenString, // string
tokenNumber,
tokenTrue,
tokenFalse,
tokenNull,
tokenArraySeparator, //数组元素分隔符 ,
tokenMemberSeparator, // 对象成员分隔符 ,
tokenComment, // 注释
tokenError // 解析出错
};
class Token {
public:
TokenType type_;
Location start_;
Location end_;
// start_ 表示标记的开始位置,end_ 表示标记的结束位置,识别出TokenType会根据这两个指针去解析对应的数据
};
我觉着在解析Object时可以帮助理解Token的作用
bool Reader::readObject(Token& token) {
Token tokenName;
String name;
Value init(objectValue); // 创建一个对象类型的初始值
currentValue().swapPayload(init); // 将当前值设置为初始化的对象值
currentValue().setOffsetStart(token.start_ - begin_); // 设置对象的起始位置
while (readToken(tokenName)) { // 读取下一个标记
bool initialTokenOk = true;
// 跳过可能存在的注释
while (tokenName.type_ == tokenComment && initialTokenOk)
initialTokenOk = readToken(tokenName);
if (!initialTokenOk)
break; // 如果读取注释失败,则退出循环
// 如果遇到对象结束标记且没有名称,则表示对象为空
if (tokenName.type_ == tokenObjectEnd && name.empty())
return true;
name.clear(); // 清除之前的名称
// 处理对象的成员名称
if (tokenName.type_ == tokenString) {
if (!decodeString(tokenName, name)) // 解析字符串名称
return recoverFromError(tokenObjectEnd); // 解析错误,恢复错误处理
} else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) {
Value numberName;
if (!decodeNumber(tokenName, numberName)) // 解析数字名称
return recoverFromError(tokenObjectEnd); // 解析错误,恢复错误处理
name = numberName.asString(); // 将数字转换为字符串作为名称
} else {
break; // 如果名称不是字符串或数字,则退出循环
}
Token colon;
// 读取冒号,检查是否存在冒号分隔符
if (!readToken(colon) || colon.type_ != tokenMemberSeparator) {
return addErrorAndRecover("Missing ':' after object member name", colon, tokenObjectEnd); // 如果缺少冒号,记录错误并恢复
}
Value& value = currentValue()[name]; // 获取当前值的对应成员
nodes_.push(&value); // 将成员值推入栈中
bool ok = readValue(); // 读取成员的值
nodes_.pop(); // 从栈中弹出成员值
if (!ok) // 如果读取值失败,错误已设置
return recoverFromError(tokenObjectEnd);
Token comma;
// 读取逗号或对象结束标记,检查是否存在有效的分隔符
if (!readToken(comma) ||
(comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator &&
comma.type_ != tokenComment)) {
return addErrorAndRecover("Missing ',' or '}' in object declaration", comma, tokenObjectEnd); // 如果缺少逗号或结束标记,记录错误并恢复
}
bool finalizeTokenOk = true;
// 跳过可能存在的注释
while (comma.type_ == tokenComment && finalizeTokenOk)
finalizeTokenOk = readToken(comma);
if (comma.type_ == tokenObjectEnd)
return true; // 如果遇到对象结束标记,则成功解析对象并返回
}
return addErrorAndRecover("Missing '}' or object member name", tokenName, tokenObjectEnd); // 如果缺少对象结束标记或成员名称,记录错误并恢复
}