使用Newtonsoft将对象数组反序列化为具体.NET类型的问题
问题描述
我有几个简化后的类:
internal class OrderItem { public string Name { get; set; } public Order Order { get; set; } } internal class Order { public IReadOnlyList<OrderItem> Items { get; set; } } internal class Container { public object[] values { get; set; } }
Container的values是object数组,因为它包含无法提前预测的不同类型元素。我使用Newtonsoft.Json进行序列化,但以下测试失败:
var order = new Order(); var orderItems = new List<OrderItem>() { new OrderItem() { Name = "One", Order = order }, new OrderItem() { Name = "Two", Order = order } }; order.Items = orderItems; var container = new Container() { values = [order, orderItems] }; var settings = new JsonSerializerSettings() { PreserveReferencesHandling = PreserveReferencesHandling.Objects, TypeNameHandling = TypeNameHandling.Objects, ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, }; var json = JsonConvert.SerializeObject(container, settings); var restored = JsonConvert.DeserializeObject<Container>(json, settings); Assert.AreEqual(typeof(Order), restored.values[0].GetType()); Assert.AreEqual(typeof(List<OrderItem>), restored.values[1].GetType());
第二个断言失败(实际类型为JArray)。我实现了如下自定义JsonConverter:
public class CustomListConverter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(object); public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; var token = JToken.Load(reader); //JArray -> List<object> if (token.Type == JTokenType.Array) { return token.ToObject<List<object>>(serializer); } else { //Removing CustomListConverter from serializer.Converters var self = serializer.Converters.FirstOrDefault(c => ReferenceEquals(c, this)); bool removed = false; if (self != null) { removed = serializer.Converters.Remove(self); } try { //and than deserialize (using default Newtonsoft.Json converters) using (var subReader = token.CreateReader()) { return serializer.Deserialize(subReader); } } finally { //Restoring CustomListConverter if it was removed if (removed && self != null) { serializer.Converters.Add(self); } } } } public override bool CanWrite => false; public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotSupportedException("This converter is read-only."); }
添加该转换器后(settings.Converters.Add(new CustomListConverter());)两个断言都能通过。我认为在反序列化前后移除并恢复CustomListConverter是为了避免循环调用,但不确定是否有更清晰的实现方式?
优化实现方案
你之前移除转换器的操作确实是为了避免递归调用——因为转换器的CanConvert匹配typeof(object),如果不临时移除,调用serializer.Deserialize时会再次触发当前转换器,导致无限循环。以下是两种更清晰的替代实现:
方式一:创建序列化器副本处理非数组类型
通过复制原序列化器的配置并排除当前转换器,避免修改原序列化器的转换器集合,逻辑更清晰且无线程安全隐患:
public class CustomListConverter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(object); public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; var token = JToken.Load(reader); return token.Type switch { JTokenType.Array => token.ToObject<List<object>>(serializer), _ => token.ToObject<object>(CreateSerializerWithoutSelf(serializer)) }; } private JsonSerializer CreateSerializerWithoutSelf(JsonSerializer original) { var serializer = new JsonSerializer { PreserveReferencesHandling = original.PreserveReferencesHandling, TypeNameHandling = original.TypeNameHandling, ConstructorHandling = original.ConstructorHandling, ContractResolver = original.ContractResolver, ReferenceLoopHandling = original.ReferenceLoopHandling // 按需复制其他配置项,如DateFormatString等 }; foreach (var converter in original.Converters.Where(c => !ReferenceEquals(c, this))) { serializer.Converters.Add(converter); } return serializer; } public override bool CanWrite => false; public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotSupportedException("This converter is read-only."); }
方式二:基于初始配置构建新序列化器
如果原序列化器的配置都来自初始的JsonSerializerSettings,也可以直接基于原设置创建新序列化器并移除当前转换器:
public class CustomListConverter : JsonConverter { private readonly JsonSerializerSettings _baseSettings; // 构造时传入初始配置 public CustomListConverter(JsonSerializerSettings baseSettings) { _baseSettings = baseSettings; } public override bool CanConvert(Type objectType) => objectType == typeof(object); public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; var token = JToken.Load(reader); if (token.Type == JTokenType.Array) { return token.ToObject<List<object>>(serializer); } else { // 基于原配置创建新序列化器,排除当前转换器 var newSettings = new JsonSerializerSettings(_baseSettings); newSettings.Converters.Remove(this); var newSerializer = JsonSerializer.Create(newSettings); using (var subReader = token.CreateReader()) { return newSerializer.Deserialize(subReader); } } } public override bool CanWrite => false; public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotSupportedException("This converter is read-only."); }
使用时需要传入初始设置:
settings.Converters.Add(new CustomListConverter(settings));
核心逻辑说明
两种方案的核心都是避免当前转换器在处理非数组类型时被递归调用:
- 原方案通过临时移除并恢复转换器来实现,但修改了全局的转换器集合,在多线程场景下可能有风险
- 优化方案通过创建独立的序列化器副本,完全隔离了当前转换器的影响,代码逻辑更直观,也更安全
内容的提问来源于stack exchange,提问作者bairog
相关产品推荐
相关产品推荐

