Newtonsoft序列化含不可序列化属性对象时的循环与异常问题排查
问题:JSON序列化含不可序列化属性对象时陷入循环并抛出异常
我原本使用以下代码做JSON序列化:
JsonSerializerSettings settings = new JsonSerializerSettings() { Culture = CultureInfo.InvariantCulture, ReferenceLoopHandling = ReferenceLoopHandling.Ignore, MaxDepth = 10 }; string serializationResult = JsonConvert.SerializeObject(currentObject, settings);
但当currentObject包含不可序列化项(比如脏DataContext引用)时会抛出异常。由于这是日志工具,我无法控制传入对象的内容,希望尽可能完成序列化、跳过有问题的属性,于是修改了代码:
try { JsonSerializerSettings settings = new JsonSerializerSettings() { Culture = CultureInfo.InvariantCulture, ReferenceLoopHandling = ReferenceLoopHandling.Ignore, MaxDepth = 10, Error = delegate (object sender, ErrorEventArgs args) { args.ErrorContext.Handled = true; } }; string serializationResult = JsonConvert.SerializeObject(currentObject, settings); return serializationResult; } catch ( Exception err) { return string.Empty; }
测试时传入带脏DataContext属性的对象,序列化直接陷入循环,数分钟后抛出错误:
System.NotSupportedException: 'Specified method is not supported.' at System.Web.HttpResponseStream.get_Length() at GetLength(Object ) at Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target)
我不清楚问题出在错误处理逻辑,还是需要过滤大对象,求解决方法。
解决方案
你的核心问题是:单纯标记ErrorContext.Handled=true不足以阻止Json.NET反复尝试序列化有问题的属性(比如HttpResponseStream这类访问属性就抛异常的类型),进而导致性能瓶颈或循环。下面是针对性的解决办法:
1. 提前过滤危险类型(推荐)
创建自定义ContractResolver,直接跳过已知不可序列化的类型或属性,从根源避免尝试序列化它们:
public class SafeContractResolver : DefaultContractResolver { // 手动维护不可序列化的类型列表 private readonly HashSet<Type> _ignoredTypes = new HashSet<Type> { typeof(System.Web.HttpResponseStream), // 添加你的DataContext类型或其他已知危险类型 }; protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); // 跳过指定类型的属性 if (_ignoredTypes.Contains(property.PropertyType)) { property.Ignored = true; return property; } // 按属性名跳过(比如DataContext相关属性) if (property.PropertyName.Equals("DataContext", StringComparison.OrdinalIgnoreCase)) { property.Ignored = true; return property; } return property; } }
然后在序列化设置中启用这个Resolver:
JsonSerializerSettings settings = new JsonSerializerSettings() { Culture = CultureInfo.InvariantCulture, ReferenceLoopHandling = ReferenceLoopHandling.Ignore, MaxDepth = 10, ContractResolver = new SafeContractResolver(), Error = delegate (object sender, ErrorEventArgs args) { args.ErrorContext.Handled = true; // 可选:记录错误日志,方便后续排查问题属性 // Logger.LogError(args.ErrorContext.Error, $"序列化属性 {args.ErrorContext.Member} 失败"); } };
2. 优化Error事件处理,移除出错属性
如果无法提前预判危险类型,可以在Error事件中直接移除出错的属性,避免Json.NET反复尝试:
Error = delegate (object sender, ErrorEventArgs args) { if (args.ErrorContext.Member != null && args.ErrorContext.Contract is JsonObjectContract contract) { // 移除出错的属性,后续不再序列化它 contract.Properties.Remove(args.ErrorContext.Member); } args.ErrorContext.Handled = true; }
3. 添加超时兜底
作为日志工具,不能因为序列化拖垮系统,给序列化操作加超时限制(Newtonsoft.Json 12.0.1+支持):
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); // 设置5秒超时 try { string serializationResult = JsonConvert.SerializeObject(currentObject, settings, cts.Token); return serializationResult; } catch (OperationCanceledException) { return "[序列化超时,已终止]"; } catch (Exception) { return string.Empty; }
内容的提问来源于stack exchange,提问作者Skary
相关产品推荐
相关产品推荐

