9.序列化后修改类结构是否影响反序列化

9.序列化后修改类结构是否影响反序列化


9.1 题目

在进行数据持久化时(存档、数据存储等功能时),如果使用Unity中的JsonUtility(Json)、C#中的BinaryFormatter(二进制)和XmlSerializer(XML)这些公共类进行序列化和反序列化时,如果在反序列化时,对应的数据结构类发生变化了,是否影响数据的读取?

遗留问题:如果是同名字段,不同类型会怎么样?


9.2 深入解析

在序列化/反序列化中,数据结构(也就是类型定义)与持久化数据之间发生「不匹配」时,不同的机制表现也有所不同。下面分别就 Unity 的 JsonUtility、.NET 的 BinaryFormatterXmlSerializer 三种常见方案,讨论它们在以下几种情况下的行为:

  1. 类新增字段
  2. 类删除字段
  3. 同名字段改了类型

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 100100.0int 100),则能读取;
    • 如果不兼容(比如 JSON 是字符串 "hello",而字段是 int),解析时该字段会保留默认值 0,并在 Unity 控制台可能出现一条警告,但不会抛异常。

2. .NET BinaryFormatter(二进制)

警告:从 .NET 5 起 BinaryFormatter 被标记为不安全并逐步弃用,建议新项目使用 System.Text.JsonDataContractSerializer 或其他更安全的方案。

  • 增加字段

    • 默认情况下,旧数据流里没有这个字段的数据,因此反序列化时,新字段会取其类型的默认值。
    • 若希望更灵活地控制版本兼容,可以在新字段上加 [OptionalField],并在 OnDeserialized 回调中初始化旧版本所需的逻辑。
  • 删除字段

    • 如果早期版本序列化时包含某字段,反序列化到新版本没有该字段的类型,会忽略这个多余的数据,不会报错。
  • 同名字段改了类型

    • 强制匹配类型:二进制流中包含字段的类型标识(包括程序集、全名、签名等),如果反序列化时发现目标字段类型与流里记录的类型不一致,会抛出 SerializationException,提示类型不匹配,导致反序列化失败。

3. .NET XmlSerializer(XML)

  • 忽略多余元素
    默认情况下,XML 里多出的元素(未在目标类型上定义 [XmlElement] 对应字段)会被静默跳过,不影响反序列化。

  • 缺少元素取默认值
    如果类型里定义了字段/属性,但 XML 中缺少对应节点,则该字段或属性保持 C# 默认值。

  • 同名字段改了类型
    XmlSerializer 在反序列化节点值时会尝试使用类型的 TypeConverter(如把字符串转换成 intDateTime 等)。

    • 可转换<hp>100</hp> 反序列化到 int hp 会成功;
    • 不可转换<hp>abc</hp> 反序列化成 int hp 时,会抛出 InvalidOperationException(内含 FormatException),导致整个反序列化流程中断。

小结与建议

情况 JsonUtility BinaryFormatter XmlSerializer
新增字段 新字段取默认值 新字段取默认值(可标 OptionalField) 新字段取默认值
删除字段 忽略多余 JSON 忽略多余二进制 忽略多余 XML
同名改类型 转换成功或默默用默认值 SerializationException InvalidOperationException
  • 版本演进最佳实践

    1. 在可控范围内,尽量只新增/删除字段,不随意更改已有字段类型或重命名;
    2. 对于二进制序列化,使用 [OptionalField][OnDeserialized] 等特性提升兼容性;
    3. 对于 JSON/XML,补充版本号字段,在反序列化后做一次「数据升级」处理,确保新版本的数据结构一致;
    4. 明确备份与回滚方案:一旦结构升级出现问题,可以快速恢复老数据。

9.3 答题示例

“不同序列化方案在结构变动时表现各异:

  • Unity JsonUtility

    • 新增字段→保留默认值;
    • 删除字段→忽略多余 JSON;
    • 同名改类型→能转换则用,否则默默保留默认值且可能有警告;
  • BinaryFormatter(已弃用):

    • 新增字段→取默认值(可加 [OptionalField]);
    • 删除字段→忽略流中多余数据;
    • 同名改类型→类型不匹配抛 SerializationException
  • XmlSerializer

    • 新增字段→取默认值;
    • 删除字段→忽略多余 XML 元素;
    • 同名改类型→可转换则成功,不可转换抛 InvalidOperationException

因此,版本演进时应尽量只增删字段,避免重命名或改类型;对二进制可用 [OptionalField],对 JSON/XML 可做数据升级处理。”


9.4 关键词联想

  • 新增字段 → 默认值
  • 删除字段 → 忽略多余
  • 同名改类型 → 转换/异常
  • JsonUtility.FromJson
  • BinaryFormatter + [OptionalField]
  • XmlSerializer + TypeConverter
  • SerializationException
  • InvalidOperationException
  • 数据升级(Versioning)
  • 序列化兼容性


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏