如何实现仅在Objects[]内完整序列化Object<T>,其余位置仅序列化ID?
实现Object的上下文感知序列化(仅数组内完整序列化)
针对你的需求:仅当Object<T>实例直接位于PersistedObject.Objects[]数组内时完整序列化,其他位置仅输出引用或ID,以下是无需修改原有类结构、不依赖序列化顺序的解决方案:
核心思路
- 提前将
Objects[]内所有实例注册到Newtonsoft的引用追踪器,确保它们被分配$id(不受序列化顺序影响)。 - 通过自定义
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
相关产品推荐
相关产品推荐

