You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Newtonsoft Json.NET自定义序列化检查,仅保存预制体变更值?

实现预制体对象仅序列化变更值的Json.NET方案

嘿,这个需求我刚好在开发游戏场景编辑器的时候踩过坑,针对预制体只保存和原始模板不一样的属性值,Json.NET有几种非常灵活的自定义判断方案,比单纯用DefaultValue特性要靠谱得多,给你分享三个实用的思路:

方案1:用ShouldSerialize[PropertyName]方法(最快上手)

Json.NET有个默认的约定:如果你的类里有命名为ShouldSerialize[属性名]的布尔方法,序列化时会自动调用这个方法来决定是否写入该属性。

举个实际的例子,假设你的预制体对象有Position和Scale两个属性,我们可以给每个属性加对应的判断方法:

public class PrefabInstance
{
    // 保存从预制体模板加载的原始值
    private Vector3 _originalPosition;
    private Vector3 _originalScale;

    public Vector3 Position { get; set; }
    public Vector3 Scale { get; set; }

    // 加载预制体时初始化原始值
    public void InitFromPrefab(PrefabTemplate template)
    {
        _originalPosition = template.Position;
        _originalScale = template.Scale;
        Position = template.Position;
        Scale = template.Scale;
    }

    // 自定义判断:只有Position和原始值不一样才序列化
    public bool ShouldSerializePosition()
    {
        return !Position.Equals(_originalPosition);
    }

    // 同理处理Scale
    public bool ShouldSerializeScale()
    {
        return !Scale.Equals(_originalScale);
    }
}

这样序列化的时候,Json.NET会自动跳过和原始预制体值一致的属性,只写入变更过的内容,完全不用额外配置序列化器。

方案2:自定义IContractResolver(全局统一处理)

如果你的预制体类型很多,不想每个类都写一堆ShouldSerialize方法,可以搞个全局的契约解析器,统一处理所有预制体对象的序列化逻辑。

首先我们可以定义一个标记接口,用来识别哪些是预制体实例:

public interface IPrefabTracked
{
    // 提供一个方法,根据属性名获取原始预制体的值
    object GetOriginalValue(string propertyName);
}

然后让你的预制体类实现这个接口,用字典存原始值会比较灵活:

public class PrefabInstance : IPrefabTracked
{
    private readonly Dictionary<string, object> _originalValues = new();

    public Vector3 Position { get; set; }
    public Vector3 Scale { get; set; }

    public void InitFromPrefab(PrefabTemplate template)
    {
        _originalValues[nameof(Position)] = template.Position;
        _originalValues[nameof(Scale)] = template.Scale;
        Position = template.Position;
        Scale = template.Scale;
    }

    // 实现接口方法,返回对应属性的原始值
    public object GetOriginalValue(string propertyName)
    {
        _originalValues.TryGetValue(propertyName, out var value);
        return value;
    }
}

接下来写自定义的契约解析器,替换默认的序列化判断逻辑:

public class PrefabContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);

        // 只处理实现了IPrefabTracked接口的类型
        if (property.DeclaringType.GetInterface(nameof(IPrefabTracked)) != null)
        {
            property.ShouldSerialize = instance =>
            {
                var prefabInstance = (IPrefabTracked)instance;
                var currentValue = member.GetValue(instance);
                var originalValue = prefabInstance.GetOriginalValue(member.Name);

                // 对比当前值和原始值,不一样才序列化
                return !object.Equals(currentValue, originalValue);
            };
        }

        return property;
    }
}

最后序列化的时候指定这个解析器就行:

var serializerSettings = new JsonSerializerSettings
{
    ContractResolver = new PrefabContractResolver(),
    Formatting = Formatting.Indented // 可选,格式化JSON方便阅读
};

var sceneJson = JsonConvert.SerializeObject(yourSceneObjects, serializerSettings);

这个方案的好处是一劳永逸,所有预制体类型都能自动套用这个逻辑,不用重复写代码。

方案3:自定义特性+契约解析器(精确控制属性)

如果你只想给部分属性做变更跟踪,不想全局处理,可以自定义一个特性,标记需要检查的属性,再结合契约解析器来处理。

先定义特性:

[AttributeUsage(AttributeTargets.Property)]
public class TrackPrefabChangeAttribute : Attribute
{
}

然后在预制体类的属性上标记:

public class PrefabInstance
{
    private Vector3 _originalPosition;
    private Vector3 _originalScale;

    [TrackPrefabChange]
    public Vector3 Position { get; set; }

    [TrackPrefabChange]
    public Vector3 Scale { get; set; }

    // 初始化原始值的逻辑...
    public void InitFromPrefab(PrefabTemplate template)
    {
        _originalPosition = template.Position;
        _originalScale = template.Scale;
        Position = template.Position;
        Scale = template.Scale;
    }
}

然后修改契约解析器,只处理带有这个特性的属性:

public class TrackedPrefabContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);

        // 检查属性是否标记了TrackPrefabChangeAttribute
        var trackAttr = member.GetCustomAttribute<TrackPrefabChangeAttribute>();
        if (trackAttr != null && member is PropertyInfo propInfo)
        {
            property.ShouldSerialize = instance =>
            {
                var prefabObj = (PrefabInstance)instance;
                object originalValue = null;
                // 根据属性名获取原始值,这里也可以用反射或者字典优化
                switch (propInfo.Name)
                {
                    case nameof(PrefabInstance.Position):
                        originalValue = prefabObj._originalPosition;
                        break;
                    case nameof(PrefabInstance.Scale):
                        originalValue = prefabObj._originalScale;
                        break;
                }
                var currentValue = propInfo.GetValue(instance);
                return !object.Equals(currentValue, originalValue);
            };
        }

        return property;
    }
}

这个方案适合你需要精确控制哪些属性需要跟踪变更的场景,灵活性很高。

一些注意点

  • 对于自定义复杂类型,一定要正确实现Equals方法,否则会导致变更判断不准确。
  • 如果原始值是引用类型,要注意做深拷贝,避免原始值被修改后判断逻辑失效。
  • 可以配合JsonSerializerSettings里的NullValueHandling = NullValueHandling.Ignore,但要确保和你的变更判断逻辑不冲突。

内容的提问来源于stack exchange,提问作者jsmars

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 04:01:59