9.序列化后修改类结构是否影响反序列化
9.1 题目
在进行数据持久化时(存档、数据存储等功能时),如果使用Unity中的JsonUtility(Json)、C#中的BinaryFormatter(二进制)和XmlSerializer(XML)这些公共类进行序列化和反序列化时,如果在反序列化时,对应的数据结构类发生变化了,是否影响数据的读取?
遗留问题:如果是同名字段,不同类型会怎么样?
9.2 深入解析
在序列化/反序列化中,数据结构(也就是类型定义)与持久化数据之间发生「不匹配」时,不同的机制表现也有所不同。下面分别就 Unity 的 JsonUtility、.NET 的 BinaryFormatter 和 XmlSerializer 三种常见方案,讨论它们在以下几种情况下的行为:
- 类新增字段
- 类删除字段
- 同名字段改了类型
1. Unity JsonUtility(JSON)
忽略多余字段
Unity 的JsonUtility.FromJson<T>()
会把 JSON 中多出来、但在目标类型T中找不到对应字段的部分直接丢弃。// 旧存档 JSON: { "hp": 100, "mp": 50, "stamina": 30 } // 新的类只定义了 hp, mp 两个字段 [Serializable] class PlayerData { public int hp; public int mp; } // 反序列化后,hp==100, mp==50,stamina 被忽略缺少字段取默认值
如果 JSON 里没有、但类里新加了字段,则该字段保持 C# 默认值(数值型为 0,引用型为 null,bool为 false)。// 新存档 JSON 只包含 hp { "hp": 80 } class PlayerData { public int hp; public int mp; } // 反序列化后,hp==80, mp==0同名字段改了类型
JsonUtility内部是基于 Unity 自己的简化 JSON 解析器,不做强类型转换也不抛出友好错误。- 如果 JSON 字段类型能被转换(比如
"100"→int 100或100.0→int 100),则能读取; - 如果不兼容(比如 JSON 是字符串
"hello",而字段是int),解析时该字段会保留默认值0,并在 Unity 控制台可能出现一条警告,但不会抛异常。
- 如果 JSON 字段类型能被转换(比如
2. .NET BinaryFormatter(二进制)
警告:从 .NET 5 起
BinaryFormatter被标记为不安全并逐步弃用,建议新项目使用System.Text.Json、DataContractSerializer或其他更安全的方案。
增加字段
- 默认情况下,旧数据流里没有这个字段的数据,因此反序列化时,新字段会取其类型的默认值。
- 若希望更灵活地控制版本兼容,可以在新字段上加
[OptionalField],并在OnDeserialized回调中初始化旧版本所需的逻辑。
删除字段
- 如果早期版本序列化时包含某字段,反序列化到新版本没有该字段的类型,会忽略这个多余的数据,不会报错。
同名字段改了类型
- 强制匹配类型:二进制流中包含字段的类型标识(包括程序集、全名、签名等),如果反序列化时发现目标字段类型与流里记录的类型不一致,会抛出
SerializationException,提示类型不匹配,导致反序列化失败。
- 强制匹配类型:二进制流中包含字段的类型标识(包括程序集、全名、签名等),如果反序列化时发现目标字段类型与流里记录的类型不一致,会抛出
3. .NET XmlSerializer(XML)
忽略多余元素
默认情况下,XML 里多出的元素(未在目标类型上定义[XmlElement]对应字段)会被静默跳过,不影响反序列化。缺少元素取默认值
如果类型里定义了字段/属性,但 XML 中缺少对应节点,则该字段或属性保持 C# 默认值。同名字段改了类型
XmlSerializer 在反序列化节点值时会尝试使用类型的TypeConverter(如把字符串转换成int、DateTime等)。- 可转换:
<hp>100</hp>反序列化到int hp会成功; - 不可转换:
<hp>abc</hp>反序列化成int hp时,会抛出InvalidOperationException(内含FormatException),导致整个反序列化流程中断。
- 可转换:
小结与建议
| 情况 | JsonUtility | BinaryFormatter | XmlSerializer |
|---|---|---|---|
| 新增字段 | 新字段取默认值 | 新字段取默认值(可标 OptionalField) | 新字段取默认值 |
| 删除字段 | 忽略多余 JSON | 忽略多余二进制 | 忽略多余 XML |
| 同名改类型 | 转换成功或默默用默认值 | 抛 SerializationException |
抛 InvalidOperationException |
版本演进最佳实践
- 在可控范围内,尽量只新增/删除字段,不随意更改已有字段类型或重命名;
- 对于二进制序列化,使用
[OptionalField]、[OnDeserialized]等特性提升兼容性; - 对于 JSON/XML,补充版本号字段,在反序列化后做一次「数据升级」处理,确保新版本的数据结构一致;
- 明确备份与回滚方案:一旦结构升级出现问题,可以快速恢复老数据。
9.3 答题示例
“不同序列化方案在结构变动时表现各异:
Unity JsonUtility:
- 新增字段→保留默认值;
- 删除字段→忽略多余 JSON;
- 同名改类型→能转换则用,否则默默保留默认值且可能有警告;
BinaryFormatter(已弃用):
- 新增字段→取默认值(可加
[OptionalField]);- 删除字段→忽略流中多余数据;
- 同名改类型→类型不匹配抛
SerializationException;XmlSerializer:
- 新增字段→取默认值;
- 删除字段→忽略多余 XML 元素;
- 同名改类型→可转换则成功,不可转换抛
InvalidOperationException。因此,版本演进时应尽量只增删字段,避免重命名或改类型;对二进制可用
[OptionalField],对 JSON/XML 可做数据升级处理。”
9.4 关键词联想
- 新增字段 → 默认值
- 删除字段 → 忽略多余
- 同名改类型 → 转换/异常
- JsonUtility.FromJson
- BinaryFormatter +
[OptionalField] - XmlSerializer +
TypeConverter SerializationExceptionInvalidOperationException- 数据升级(Versioning)
- 序列化兼容性
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com