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

如何实现仅在Objects[]内完整序列化Object<T>,其余位置仅序列化ID?

实现Object的上下文感知序列化(仅数组内完整序列化)

针对你的需求:仅当Object<T>实例直接位于PersistedObject.Objects[]数组内时完整序列化,其他位置仅输出引用或ID,以下是无需修改原有类结构、不依赖序列化顺序的解决方案:

核心思路

  1. 提前将Objects[]内所有实例注册到Newtonsoft的引用追踪器,确保它们被分配$id(不受序列化顺序影响)。
  2. 通过自定义JsonConverter和ContractResolver,判断当前序列化的Object<T>是否处于Objects[]外,若是则输出$ref指向数组内的实例;数组内的实例保持完整序列化。

完整实现代码

1. 自定义ContractResolver(标记序列化上下文)

public class ObjectHierarchyContractResolver : DefaultContractResolver
{
    private bool _isSerializingObjectsArray;

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);

        // 识别PersistedObject的Objects数组属性
        if (member.DeclaringType == typeof(PersistedObject) && member.Name == nameof(PersistedObject.Objects))
        {
            property.ValueProvider = new ObjectArrayValueProvider(property.ValueProvider, this);
        }
        // 对Objects数组外的ObjectBase子类应用引用转换器
        else if (typeof(ObjectBase).IsAssignableFrom(property.PropertyType) && !_isSerializingObjectsArray)
        {
            property.Converter = new ObjectReferenceConverter();
        }

        return property;
    }

    internal void SetSerializingObjectsArray(bool value) => _isSerializingObjectsArray = value;

    // 包装Objects数组的ValueProvider,标记序列化上下文
    private class ObjectArrayValueProvider : IValueProvider
    {
        private readonly IValueProvider _originalProvider;
        private readonly ObjectHierarchyContractResolver _resolver;

        public ObjectArrayValueProvider(IValueProvider originalProvider, ObjectHierarchyContractResolver resolver)
        {
            _originalProvider = originalProvider;
            _resolver = resolver;
        }

        public void SetValue(object target, object value) => _originalProvider.SetValue(target, value);

        public object GetValue(object target)
        {
            var objects = _originalProvider.GetValue(target);
            // 标记当前正在序列化Objects数组
            _resolver.SetSerializingObjectsArray(true);
            return objects;
        }
    }
}

2. 自定义Converter(输出引用或ID)

public class ObjectReferenceConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) => typeof(ObjectBase).IsAssignableFrom(objectType);

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var obj = (ObjectBase)value;
        var referenceResolver = serializer.ReferenceResolver;

        // 若实例已注册到引用追踪器,输出$ref;否则直接输出ID
        if (referenceResolver.IsReferenced(serializer, obj))
        {
            writer.WriteStartObject();
            writer.WritePropertyName("$ref");
            writer.WriteValue(referenceResolver.GetReference(serializer, obj));
            writer.WriteEndObject();
        }
        else
        {
            writer.WriteStartObject();
            writer.WritePropertyName("ID");
            writer.WriteValue(obj.ID);
            writer.WriteEndObject();
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // 反序列化时根据ID或引用获取实例,复用现有服务逻辑
        var jObj = JObject.Load(reader);
        if (jObj.ContainsKey("$ref"))
        {
            var refId = jObj["$ref"].Value<string>();
            return serializer.ReferenceResolver.ResolveReference(serializer, refId);
        }
        else
        {
            var id = jObj["ID"].Value<int>();
            return SomeService.ResolveObjectById(objectType, id);
        }
    }
}

3. 序列化方法(提前注册引用)

public static string SerializePersistedObject(PersistedObject persistedObj)
{
    var settings = new JsonSerializerSettings
    {
        PreserveReferencesHandling = PreserveReferencesHandling.Objects,
        ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
        ContractResolver = new ObjectHierarchyContractResolver()
    };

    var serializer = JsonSerializer.Create(settings);
    var referenceResolver = serializer.ReferenceResolver as DefaultReferenceResolver;

    // 提前将Objects数组内的所有实例注册到引用追踪器,确保分配$id
    foreach (var obj in persistedObj.Objects)
    {
        if (!referenceResolver.IsReferenced(serializer, obj))
        {
            referenceResolver.AddReference(serializer, obj, referenceResolver.GetReference(serializer, obj));
        }
    }

    return JsonConvert.SerializeObject(persistedObj, settings);
}

效果验证

无论Objects[]内ObjectA和ObjectB的顺序如何,序列化结果都会符合预期:

{
  "Objects": [
    {
      "$id": "1",
      "ID": 1,
      "RefToSomeBInstance": {
        "$ref": "2"
      }
    },
    {
      "$id": "2",
      "ID": 2
    }
  ]
}

方案优势

  • 无需修改原有类结构,保留代码简洁性和开发体验。
  • 不受序列化顺序影响,提前注册引用确保数组内实例优先获得$id。
  • 通过上下文标记精准区分数组内/外的序列化行为,逻辑清晰。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 18:07:09