AIGCJson 库解析行为与异常处理指南
目录
概述
AIGCJson 是一个基于 RapidJSON 的 C++ JSON 序列化/反序列化库,通过宏定义简化结构体与 JSON 之间的转换。
核心特性
- 支持基本类型:
int,bool,float,double,string等 - 支持容器类型:
vector,list,map,set等 - 支持嵌套结构体
- 通过
AIGC_JSON_HELPER宏自动生成序列化代码
基本工作原理
1. 结构体定义
cpp
struct MyStruct {
std::string name;
int age;
bool is_active;
// 使用宏注册字段,用于序列化/反序列化
AIGC_JSON_HELPER(name, age, is_active);
};
2. 整体解析流程图
否
是
否
是
否
是
开始: JsonToObject调用
解析JSON字符串
JSON解析成功?
返回false: JSON格式错误
创建JsonHelperPrivate对象
调用结构体的AIGCJsonToObject
获取字段名称列表
开始字段遍历: index=0
还有字段未处理?
返回true: 解析成功
处理当前字段
字段解析成功?
返回false: 停止解析
index++
3. 解析流程说明
当调用 JsonToObject 解析 JSON 时:
- JSON 解析 :将 JSON 字符串解析为 RapidJSON 的
Value对象 - 字段遍历 :按照
AIGC_JSON_HELPER中声明的顺序逐个处理字段 - 类型检查:对每个字段进行类型匹配检查
- 值设置:类型匹配成功则设置值,失败则记录错误并停止
4. 解析顺序
字段按照 AIGC_JSON_HELPER 中声明的顺序从左到右依次解析。
示例:
AIGC_JSON_HELPER(name, age, is_active)
↓ ↓ ↓
第1个 第2个 第3个
字段解析规则
规则 1:单个字段解析流程图
是
否
否
是
否
是
否
是
否
是
开始处理字段
JSON对象是否为null?
返回true: 跳过设置
JSON是否为对象类型?
返回false: 类型错误
字段是否存在?
有默认值?
使用默认值
保持原值
返回true: 继续下一个字段
调用JsonToObject解析字段值
解析成功?
记录错误信息
返回false: 停止解析
设置字段值
返回true: 继续下一个字段
规则 2:字段存在性检查
| JSON 字段状态 | 行为 | 返回值 |
|---|---|---|
| 字段存在且类型匹配 | 解析并设置值 | true |
| 字段存在但类型不匹配 | 解析失败,记录错误 | false |
| 字段不存在 | 使用默认值(如果有),否则保持原值 | true |
规则 2:字段缺失处理
当 JSON 中某个字段不存在时:
- 如果字段有默认值 (通过
AIGC_JSON_HELPER_DEFAULT设置),使用默认值 - 如果字段没有默认值,保持 C++ 结构体的初始化值
- 不会导致解析失败,继续处理后续字段
示例:
cpp
struct User {
std::string name;
int age = 18; // 默认值
bool is_vip = false; // 默认值
AIGC_JSON_HELPER(name, age, is_vip);
};
// JSON: {"name": "Alice"}
// 解析结果:
// - name = "Alice" ✅
// - age = 18 ✅ (使用默认值)
// - is_vip = false ✅ (使用默认值)
null 值处理
null 值处理流程图
string
bool/int/float/double
结构体
遇到null值
字段类型是什么?
特殊处理
设置为空字符串
返回true: 解析成功
类型检查
IsBool/IsInt/IsNumber返回false
记录错误信息
返回false: 解析失败
递归处理
遇到null直接返回
所有字段保持默认值
返回true: 解析成功
不同类型对 null 的处理
| 数据类型 | null 值行为 | 结果 |
|---|---|---|
std::string |
特殊处理,解析成功 | 设置为空字符串 "" |
bool |
类型检查失败 | 解析失败,返回 false |
int, float, double |
类型检查失败 | 解析失败,返回 false |
| 自定义结构体 | 递归处理,遇到 null 直接返回 | 解析成功,所有字段保持默认值 |
null 值处理对比图
┌─────────────────────────────────────────────────────────────┐
│ JSON: {"name": null, "age": null, "is_vip": null} │
└─────────────────────────────────────────────────────────────┘
↓
┌───────────────────┼───────────────────┐
↓ ↓ ↓
string类型 int类型 bool类型
↓ ↓ ↓
特殊处理 ✅ 类型检查 ❌ 类型检查 ❌
↓ ↓ ↓
name = "" age解析失败 is_vip解析失败
继续解析 停止解析 停止解析
结果:
string类型:解析成功,继续处理后续字段int/bool类型:解析失败,立即停止,后续字段不处理
string 类型的特殊处理
std::string 是唯一对 null 有特殊处理的基本类型:
cpp
bool JsonToObject(std::string &obj, rapidjson::Value &jsonValue)
{
obj = "";
if (jsonValue.IsNull())
return true; // ✅ null 时成功解析为空字符串
// ...
}
示例:
json
{
"name": null,
"age": 25
}
解析结果:
name=""(空字符串,解析成功)age=25(正常解析)
基本类型的 null 处理
对于 bool, int, float, double 等基本类型,null 会导致解析失败:
cpp
bool JsonToObject(bool &obj, rapidjson::Value &jsonValue)
{
if (!jsonValue.IsBool()) // null 时 IsBool() 返回 false
{
m_message = "json-value is ... but object is bool.";
return false; // ❌ 解析失败
}
// ...
}
示例:
json
{
"is_active": null,
"name": "Test"
}
解析结果:
is_active:解析失败,保持默认值(如false)name:不会继续解析(因为前一个字段失败)
结构体类型的 null 处理
当结构体字段为 null 时:
cpp
// 在 SetMembers 中
if (jsonValue.IsNull())
return true; // 直接返回,不设置任何值
示例:
cpp
struct Address {
std::string city;
std::string street;
AIGC_JSON_HELPER(city, street);
};
struct User {
std::string name;
Address address; // 结构体字段
AIGC_JSON_HELPER(name, address);
};
json
{
"name": "Alice",
"address": null
}
解析结果:
name="Alice"✅address.city=""(默认值)address.street=""(默认值)- 整体解析成功 ✅
解析失败行为
核心机制:短路停止流程图
否
是
否
是
否
是
否
是
开始解析结构体
处理字段1: name
字段1解析成功?
❌ 立即停止
✅ 字段1已设置值
处理字段2: age
字段2解析成功?
❌ 立即停止
✅ 字段2已设置值
处理字段3: is_vip
字段3解析成功?
❌ 立即停止
✅ 字段3已设置值
处理字段4: email
字段4解析成功?
❌ 立即停止
✅ 所有字段解析完成
返回true: 解析成功
返回false: 解析失败
后续字段保持默认值
核心机制:短路停止
重要特性 :当遇到第一个解析失败的字段时,立即停止 ,后续字段不会继续解析。
代码逻辑
cpp
template <typename TYPE, typename... TYPES>
bool SetMembers(..., TYPE &arg, TYPES &...args)
{
// 处理第一个字段
if (!SetMembers(..., arg))
return false; // ❌ 失败则立即返回,不继续
// 只有成功才继续处理后续字段
return SetMembers(..., args...);
}
可视化示例:解析过程对比
场景 A:所有字段成功解析
JSON: {"name": "Alice", "age": 25, "is_vip": true, "email": "alice@example.com"}
解析过程:
┌─────────────────────────────────────────────────────────┐
│ 字段1: name = "Alice" ✅ 成功 → 继续 │
│ 字段2: age = 25 ✅ 成功 → 继续 │
│ 字段3: is_vip = true ✅ 成功 → 继续 │
│ 字段4: email = "alice@..." ✅ 成功 → 完成 │
└─────────────────────────────────────────────────────────┘
结果: ✅ 全部成功,所有字段都有值
场景 B:第二个字段失败
JSON: {"name": "Alice", "age": null, "is_vip": true, "email": "alice@example.com"}
解析过程:
┌─────────────────────────────────────────────────────────┐
│ 字段1: name = "Alice" ✅ 成功 → 继续 │
│ 字段2: age = null ❌ 失败 → 立即停止 │
│ 字段3: is_vip ⏸️ 未处理(保持默认值) │
│ 字段4: email ⏸️ 未处理(保持默认值) │
└─────────────────────────────────────────────────────────┘
结果: ❌ 解析失败
- name = "Alice" (已解析)
- age = 0 (默认值,未解析)
- is_vip = false (默认值,未解析)
- email = "" (默认值,未解析)
实际影响
| 场景 | 行为 | 结果 |
|---|---|---|
| 字段 1 解析失败 | 立即停止 | 字段 1 保持默认值,字段 2-N 保持默认值 |
| 字段 2 解析失败 | 立即停止 | 字段 1 有值,字段 2 保持默认值,字段 3-N 保持默认值 |
| 所有字段成功 | 全部处理 | 所有字段都有值 |
示例场景
cpp
struct User {
std::string name;
int age;
bool is_vip;
std::string email;
AIGC_JSON_HELPER(name, age, is_vip, email);
};
场景 1:第二个字段失败
json
{
"name": "Alice",
"age": null, // ❌ int 类型不能是 null
"is_vip": true,
"email": "alice@example.com"
}
解析过程可视化:
步骤1: 处理 name 字段
┌─────────────────────────────────────┐
│ JSON字段: "name": "Alice" │
│ 类型检查: string ✅ │
│ 设置值: name = "Alice" │
│ 结果: ✅ 成功,继续下一个字段 │
└─────────────────────────────────────┘
步骤2: 处理 age 字段
┌─────────────────────────────────────┐
│ JSON字段: "age": null │
│ 类型检查: int ❌ (null不是int类型) │
│ 设置值: age = 0 (保持默认值) │
│ 结果: ❌ 失败,立即停止 │
└─────────────────────────────────────┘
步骤3-4: 不再处理
┌─────────────────────────────────────┐
│ is_vip: 未处理,保持默认值 false │
│ email: 未处理,保持默认值 "" │
└─────────────────────────────────────┘
解析结果:
name="Alice"✅(已解析)age=0(默认值,解析失败后保持)is_vip=false(默认值,未解析)email=""(默认值,未解析)
解析函数返回 :false(失败)
场景 2:所有字段成功
json
{
"name": "Alice",
"age": 25,
"is_vip": true,
"email": "alice@example.com"
}
解析过程可视化:
┌─────────────────────────────────────────────────────────┐
│ 字段1: name │
│ JSON值: "Alice" │
│ 类型: string ✅ │
│ 结果: name = "Alice" → 继续 │
├─────────────────────────────────────────────────────────┤
│ 字段2: age │
│ JSON值: 25 │
│ 类型: int ✅ │
│ 结果: age = 25 → 继续 │
├─────────────────────────────────────────────────────────┤
│ 字段3: is_vip │
│ JSON值: true │
│ 类型: bool ✅ │
│ 结果: is_vip = true → 继续 │
├─────────────────────────────────────────────────────────┤
│ 字段4: email │
│ JSON值: "alice@example.com" │
│ 类型: string ✅ │
│ 结果: email = "alice@example.com" → 完成 │
└─────────────────────────────────────────────────────────┘
✅ 所有字段解析成功
解析结果:
- 所有字段都有值 ✅
- 解析函数返回:
true(成功)
场景 3:字段缺失(不是 null)
json
{
"name": "Alice"
// 其他字段缺失
}
解析过程可视化:
┌─────────────────────────────────────────────────────────┐
│ 字段1: name │
│ JSON值: "Alice" │
│ 存在: ✅ │
│ 结果: name = "Alice" → 继续 │
├─────────────────────────────────────────────────────────┤
│ 字段2: age │
│ JSON值: 不存在 │
│ 默认值: 0 ✅ │
│ 结果: age = 0 (使用默认值) → 继续 │
├─────────────────────────────────────────────────────────┤
│ 字段3: is_vip │
│ JSON值: 不存在 │
│ 默认值: false ✅ │
│ 结果: is_vip = false (使用默认值) → 继续 │
├─────────────────────────────────────────────────────────┤
│ 字段4: email │
│ JSON值: 不存在 │
│ 默认值: "" ✅ │
│ 结果: email = "" (使用默认值) → 完成 │
└─────────────────────────────────────────────────────────┘
✅ 所有字段处理完成(字段缺失不算错误)
解析结果:
name="Alice"✅age=0(默认值,字段缺失不影响)is_vip=false(默认值,字段缺失不影响)email=""(默认值,字段缺失不影响)
解析函数返回 :true(成功,字段缺失不算错误)
最佳实践
1. 设置合理的默认值
为所有字段设置合理的默认值,避免解析失败时出现意外值:
cpp
struct User {
std::string name;
int age = 0; // ✅ 设置默认值
bool is_vip = false; // ✅ 设置默认值
std::string email;
AIGC_JSON_HELPER(name, age, is_vip, email);
};
2. 服务端避免返回 null
对于基本类型(bool, int, float 等),服务端应避免返回 null:
不推荐:
json
{
"is_active": null,
"age": null
}
推荐:
json
{
"is_active": false, // 返回实际值
"age": 0 // 或省略该字段
}
3. 检查解析结果
始终检查 JsonToObject 的返回值:
cpp
User user;
std::string jsonStr = "...";
std::string errorMsg;
if (!aigc::Json::JsonToObject(user, jsonStr, {}, &errorMsg)) {
// 处理解析失败
LOG_ERROR("解析失败: %s", errorMsg.c_str());
// 使用默认值或返回错误
return false;
}
// 解析成功,使用 user 对象
4. 使用可选字段模式
对于可能为 null 的字段,考虑使用 std::optional(需要自定义处理)或通过业务逻辑判断:
cpp
struct User {
std::string name;
std::string email;
// 通过检查 email 是否为空来判断是否为 null
bool hasEmail() const { return !email.empty(); }
AIGC_JSON_HELPER(name, email);
};
5. 字段顺序考虑
将关键字段 放在前面,可选字段放在后面:
cpp
struct User {
std::string id; // ✅ 关键字段在前
std::string name; // ✅ 关键字段在前
int age = 0; // 可选字段
bool is_vip = false; // 可选字段
AIGC_JSON_HELPER(id, name, age, is_vip);
};
这样即使后面的字段解析失败,关键字段也能正确解析。
常见问题
Q1: 为什么 string 类型的 null 能成功解析,但 bool 不行?
A : 这是库的设计决策。string 类型对 null 有特殊处理,会解析为空字符串,而其他基本类型(bool, int 等)严格检查类型,null 不符合类型要求。
Q2: 解析失败后,已解析的字段值会保留吗?
A : 会保留。解析是顺序进行的,已成功解析的字段值会被设置,失败时停止,后续字段保持默认值。
Q3: 字段缺失和字段为 null 有什么区别?
A:
- 字段缺失:不会导致解析失败,使用默认值,继续处理后续字段
- 字段为 null :
string类型:解析为空字符串,继续处理- 其他基本类型:解析失败,停止处理
- 结构体类型:解析成功(所有字段保持默认值),继续处理
Q4: 如何判断字段是缺失还是 null?
A : 对于 string 类型,无法区分(都是空字符串)。对于其他类型,如果解析失败,可能是 null 或类型不匹配。建议在服务端统一处理,避免返回 null。
Q5: 解析失败时如何获取错误信息?
A : 使用 JsonToObject 的 message 参数:
cpp
std::string errorMsg;
if (!aigc::Json::JsonToObject(obj, jsonStr, {}, &errorMsg)) {
// errorMsg 包含详细的错误信息
std::cout << "错误: " << errorMsg << std::endl;
}
Q6: 能否让解析失败时继续处理后续字段?
A: 当前版本的 AIGCJson 不支持。如果需要这个功能,需要修改库源码或使用其他 JSON 库。
总结
核心要点速查表
| 要点 | 说明 | 影响 |
|---|---|---|
| 解析顺序 | 按照 AIGC_JSON_HELPER 声明的顺序从左到右解析 |
关键字段应放在前面 |
| 短路停止 | 遇到第一个解析失败的字段立即停止 | 后续字段不会处理 |
| null 处理 | 只有 string 类型对 null 有特殊处理,其他类型会失败 |
服务端应避免返回 null |
| 字段缺失 | 不算错误,使用默认值并继续处理 | 字段缺失是安全的 |
| 默认值重要性 | 为所有字段设置合理的默认值 | 避免解析失败时出现意外值 |
决策流程图
否
是
否
是
string
其他基本类型
结构体
是
否
是
否
开始解析JSON
字段存在?
使用默认值
继续下一个字段
字段值为null?
类型匹配?
字段类型?
设置为空字符串
解析失败
所有字段保持默认值
停止解析
设置字段值
还有字段?
解析完成
返回false
返回true
建议
- ✅ 为所有字段设置默认值
- ✅ 服务端避免返回 null(特别是基本类型)
- ✅ 检查解析结果并处理错误
- ✅ 将关键字段放在前面
- ✅ 使用错误消息参数获取详细错误信息
快速参考卡片
┌─────────────────────────────────────────────────────────┐
│ AIGCJson 解析行为快速参考 │
├─────────────────────────────────────────────────────────┤
│ │
│ ✅ 字段缺失 → 使用默认值,继续解析 │
│ ✅ string null → 解析为空字符串,继续解析 │
│ ❌ bool null → 解析失败,立即停止 │
│ ❌ int null → 解析失败,立即停止 │
│ ✅ 结构体 null → 所有字段默认值,继续解析 │
│ ❌ 类型不匹配 → 解析失败,立即停止 │
│ │
│ 关键原则:遇到第一个失败立即停止,后续字段不处理 │
│ │
└─────────────────────────────────────────────────────────┘
文档创建时间:2026-01-10
基于 AIGCJson 库源码分析整理