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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 22:25:18