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

移除.NET异常旧序列化模式后序列化失效,是否为有意设计?

自定义异常序列化问题:移除旧序列化模式后的反序列化失效

问题现象

移除自定义异常类的[Serializable]属性、受保护的序列化构造函数及GetObjectData方法后,出现以下问题:

  • 直接实例化的异常:Newtonsoft.Json和System.Text.Json反序列化时都无法还原原异常消息,取而代之的是默认提示:Exception of type '....SomeIdNotFoundException' was thrown.
  • 保留旧序列化模式时:
    • Newtonsoft.Json可完整反序列化,保留原消息及其他异常状态;
    • System.Text.Json仍无法还原原消息,表现与移除模式后一致。
  • 抛出的异常:
    • System.Text.Json无论是否保留旧模式,都会抛出NotSupportedException,提示无法序列化System.Reflection.MethodBase(对应异常的TargetSite属性);
    • Newtonsoft.Json只要保留旧序列化模式,就能正常处理抛出的异常。

原因分析

  1. .NET异常类的特殊序列化机制
    System.Exception及其子类依赖ISerializable接口(通过[Serializable]标记、序列化构造函数和GetObjectData方法)实现完整状态的序列化。这类类型包含大量私有、只读或受保护的核心成员(如Message、StackTrace),常规的属性序列化逻辑无法访问和还原这些成员。

  2. Newtonsoft.Json的处理逻辑

    • 当类标记[Serializable]并实现ISerializable时,Newtonsoft会优先调用GetObjectData序列化所有内部状态,反序列化时通过受保护的构造函数还原状态;
    • 无[Serializable]标记时,Newtonsoft只能通过公共构造函数和属性处理,而异常的Message等字段是只读的,反序列化时只能创建新实例并使用默认消息。
  3. System.Text.Json的限制

    • 默认不支持ISerializable接口(.NET 6及之前版本),即使保留[Serializable]标记,也不会触发异常的特殊序列化逻辑;
    • 抛出的异常包含TargetSite属性(指向MethodBase实例),而System.Text.Json默认不序列化反射类型,直接报错。

解决方案

方案1:保留旧序列化模式(兼容Newtonsoft.Json)

如果需要继续支持Newtonsoft.Json的完整异常序列化,建议暂时保留[Serializable]属性、序列化构造函数和GetObjectData方法。尽管这些API被标记为过时,但在Newtonsoft.Json生态下仍是实现异常完整序列化的必要条件。

方案2:为System.Text.Json自定义异常转换器

若要迁移到System.Text.Json,需自定义JsonConverter来手动处理异常的序列化与反序列化,示例代码如下:

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

public class ExceptionConverter<T> : JsonConverter<T> where T : Exception
{
    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (!JsonDocument.TryParseValue(ref reader, out var doc))
            throw new JsonException("无法解析异常数据");

        var root = doc.RootElement;
        var message = root.TryGetProperty("Message", out var msgProp) ? msgProp.GetString() : null;
        var innerException = root.TryGetProperty("InnerException", out var innerProp) 
            ? JsonSerializer.Deserialize<Exception>(innerProp.GetRawText(), options) 
            : null;
        var stackTrace = root.TryGetProperty("StackTrace", out var stackProp) ? stackProp.GetString() : null;
        var source = root.TryGetProperty("Source", out var sourceProp) ? sourceProp.GetString() : null;

        var exception = (T)Activator.CreateInstance(typeToConvert, message, innerException)!;
        exception.Source = source;
        // 通过反射设置只读的StackTrace字段
        typeof(Exception).GetField("_stackTraceString", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)?
            .SetValue(exception, stackTrace);

        return exception;
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WriteString("Message", value.Message);
        if (value.InnerException != null)
        {
            writer.WritePropertyName("InnerException");
            JsonSerializer.Serialize(writer, value.InnerException, options);
        }
        writer.WriteString("StackTrace", value.StackTrace);
        writer.WriteString("Source", value.Source);
        writer.WriteEndObject();
    }
}

使用方式:

var options = new JsonSerializerOptions();
options.Converters.Add(new ExceptionConverter<SomeIdNotFoundException>());

// 序列化
var serialized = JsonSerializer.Serialize(originalException, options);
// 反序列化
var deserialized = JsonSerializer.Deserialize<SomeIdNotFoundException>(serialized, options);

方案3:处理抛出异常的System.Text.Json序列化问题

对于抛出的异常,需排除TargetSite等无法序列化的属性,可通过配置忽略只读属性或在转换器中跳过这些字段:

var options = new JsonSerializerOptions
{
    IgnoreReadOnlyProperties = true,
    Converters = { new ExceptionConverter<SomeIdNotFoundException>() }
};

内容的提问来源于stack exchange,提问作者Erik Hart

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 21:13:18