This question refers to code which still uses Newtonsoft.Json. Following tests also made use of System.Text.Json.
I finally wanted to remove the obsolete pattern, with [SerializableAttribute]
, protected ctor and GetObjectData(SerializationInfo, StreamingContext)
from my exceptions.
Now serializers, both System.Text.Json.JsonSerializer
and Newtonsoft.Json.JsonConvert
don’t deserialize the exception properly:
although not relying on old IFormatter
(really not?), [edit] Newtonsoft.Json serialized and deserialized exceptions implementing the old pattern. Now, deserializing not even reproduces the original exception message, but a new message is created when deserializing SomeIdNotFoundException
:
Exception of type ‘….SomeIdNotFoundException’ was thrown.
I see that exception classes, both custom and common .NET types, including the basic System.Exception
, don’t fit the usual requirements for (de)serialization, by having private/protected and read-only members, but is it intentional to lose all exception content, unless using special configurations?
Edit: Example
With legacy serialization
using System;
using System.Runtime.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[Serializable]
public class SomeIdNotFoundException : Exception
{
public SomeIdNotFoundException()
{ }
public SomeIdNotFoundException(string message)
: base(message)
{ }
public SomeIdNotFoundException(string message, Exception innerException)
: base(message, innerException)
{ }
protected SomeIdNotFoundException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
}
[TestClass]
public class ExceptionSerializationTests
{
[TestMethod]
public void SomeIdNotFoundException_Serialization_RoundTrip()
{
var expectedMessage = "Some ID not found.";
var originalException = new SomeIdNotFoundException(expectedMessage);
//var originalException = Assert.ThrowsException<SomeIdNotFoundException>(
// () => throw new SomeIdNotFoundException(expectedMessage));
var deserializedException = SerializeAndDeserialize(originalException);
Assert.AreEqual(expectedMessage, deserializedException.Message);
}
private static SomeIdNotFoundException SerializeAndDeserialize(SomeIdNotFoundException exceptionToSerialize)
{
//var serializedException = System.Text.Json.JsonSerializer.Serialize(exceptionToSerialize);
//return System.Text.Json.JsonSerializer.Deserialize<SomeIdNotFoundException>(serializedException)!;
var serializedException = Newtonsoft.Json.JsonConvert.SerializeObject(exceptionToSerialize);
return Newtonsoft.Json.JsonConvert.DeserializeObject<SomeIdNotFoundException>(serializedException)!;
}
public static void Main(string[] args)
{
new ExceptionSerializationTests()
.SomeIdNotFoundException_Serialization_RoundTrip();
}
}
Without legacy serialization
Exception class only.
// no Serializable attribute
public class SomeIdNotFoundException : Exception
{
public SomeIdNotFoundException()
{ }
public SomeIdNotFoundException(string message)
: base(message)
{ }
public SomeIdNotFoundException(string message, Exception innerException)
: base(message, innerException)
{ }
}
Direct exception instantiation (no throw)
With legacy pattern disabled
Deserialization with both, System and Newtonsoft, serializers generates a new exception message of this kind:
Exception of type ‘[namespace].SomeIdNotFoundException’ was thrown.
With legacy pattern still available
- System.Text.Json behaves like without legacy pattern, creating a deserialized exception with a new exception message.
- Newtonsoft.Json performs a full deserialization of the method, preserving the original message.
Thrown exception
Throwing the exception, like in the commented-out part with Assert.ThrowsException<SomeIdNotFoundException>(...)
. Then, deserialization never works with System.Text.Json, no matter if the legacy pattern is implemented.
Error messages for thrown exception with System.Text.Json:
System.NotSupportedException: Serialization and deserialization of ‘System.Reflection.MethodBase’ instances is not supported. Path: $.TargetSite. —>
System.NotSupportedException: Serialization and deserialization of ‘System.Reflection.MethodBase’ instances is not supported.
Newtonsoft.Json has no problem with thrown exceptions, as long as the legacy pattern is available.
16
Answering my own question:
- Newtonsoft.Json supports the legacy serialization pattern for exceptions.
- System.Text.Json never supported exception serialization by default.
Without the legacy pattern, or specific coding, none of these JSON serializations support exception serialization.
See also links from @dbc in the comments under the question!