JsonConvert.PopulateObject填充自定义可序列化类时值被重置问题
Unity JsonConvert.PopulateObject 自定义类字段填充异常问题解析
问题背景
- 现有系统通过
JsonConvert.PopulateObject将JSON填充回ScriptableObject,顶层列表、数组等类型工作正常 - 自定义可序列化类
UpgradeLevel(非ScriptableObject子类)出现异常:未标记JsonProperty或标记JsonIgnore的字段(如GameObject visual、int cost、Vector3 test)填充后被重置为默认值(visual为null、cost为0、test为(0,0,0)) - 给
cost和test标记JsonProperty后可正常取值,但visual(GameObject引用)始终变为null - 需求:避免为每个自定义类编写转换器,实现批量兼容
异常原因分析
1. 自定义契约解析器的过滤逻辑
你使用的NoPropertiesContractResolver大概率会过滤未标记JsonProperty的字段。Newtonsoft.Json默认会自动识别类的可序列化字段,但自定义契约解析器如果重写了字段筛选逻辑,会导致未标记特性的字段无法被反序列化框架识别。再加上设置了ObjectCreationHandling = ObjectCreationHandling.Replace,填充时未被识别的字段会被强制重置为默认值。
2. Unity引用类型的序列化限制
Newtonsoft.Json本身无法直接序列化/反序列化Unity的Object子类(如GameObject),这类对象的引用是Unity编辑器/运行时的内部实例,无法通过纯文本JSON存储和直接恢复。即使标记了JsonProperty,JSON中只能存储对象的名称或路径,反序列化时无法自动映射回场景/资源中的实际实例。
解决方案
一、修复自定义类字段被重置问题
1. 调整契约解析器逻辑
修改NoPropertiesContractResolver,让它自动包含所有可序列化字段(无需手动标记JsonProperty):
public class NoPropertiesContractResolver : DefaultContractResolver { protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { // 仅保留字段,排除属性 var properties = base.CreateProperties(type, memberSerialization) .Where(p => p.MemberType == MemberTypes.Field) .ToList(); // 补充所有未被筛选到的可序列化字段(排除标记JsonIgnore的) foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(f => f.IsSerializable && !Attribute.IsDefined(f, typeof(JsonIgnoreAttribute)))) { if (!properties.Any(p => p.PropertyName == field.Name)) { properties.Add(CreateProperty(field, memberSerialization)); } } return properties; } }
2. 修改对象创建策略
将ObjectCreationHandling从Replace改为Auto,这样反序列化时会优先复用现有对象的字段,而非直接替换整个对象导致未处理字段被重置:
ObjectCreationHandling = ObjectCreationHandling.Auto
二、统一处理Unity引用类型(GameObject)
编写一个通用的Unity对象转换器,无需为每个类单独编写:
public class UnityObjectConverter : JsonConverter { public override bool CanConvert(Type objectType) { return typeof(UnityEngine.Object).IsAssignableFrom(objectType); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; string assetPath = reader.Value.ToString(); // 编辑器下通过路径加载资源,运行时需改为从AssetBundle或Resources加载 return AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var obj = value as UnityEngine.Object; if (obj == null) { writer.WriteNull(); return; } // 存储资源路径,仅编辑器有效,运行时可替换为InstanceID或其他唯一标识 string assetPath = AssetDatabase.GetAssetPath(obj); writer.WriteValue(assetPath); } }
将该转换器加入到CreateConverters()的返回列表中,即可统一处理所有Unity Object类型的序列化/反序列化。
内容的提问来源于stack exchange,提问作者Steven Goulet
相关产品推荐
相关产品推荐

