移除.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只要保留旧序列化模式,就能正常处理抛出的异常。
- System.Text.Json无论是否保留旧模式,都会抛出
原因分析
.NET异常类的特殊序列化机制
System.Exception及其子类依赖ISerializable接口(通过[Serializable]标记、序列化构造函数和GetObjectData方法)实现完整状态的序列化。这类类型包含大量私有、只读或受保护的核心成员(如Message、StackTrace),常规的属性序列化逻辑无法访问和还原这些成员。Newtonsoft.Json的处理逻辑
- 当类标记
[Serializable]并实现ISerializable时,Newtonsoft会优先调用GetObjectData序列化所有内部状态,反序列化时通过受保护的构造函数还原状态; - 无
[Serializable]标记时,Newtonsoft只能通过公共构造函数和属性处理,而异常的Message等字段是只读的,反序列化时只能创建新实例并使用默认消息。
- 当类标记
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
相关产品推荐
相关产品推荐

