Reputation: 101
I've been having trouble deserializing custom exceptions in Newtonsoft.Json version 11.0.2. It works fine in Newtonsoft.Json version 10.0.3.
I serialize and deserialize using -
result = JsonConvert.SerializeObject( <<object of type MyHttpException>> );
MyHttpException deserializedException = JsonConvert.DeserializeObject<MyHttpException>(result);
The error I get during deserialization is a Newtonsoft.Json.JsonSerializationException
:
Unable to find a constructor to use for type MyHttpException. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'HttpStatusCode', line 2, position 19.
If I add a parameterless constructor to MyHttpException and MyBaseException, I don't get any exception. But the InnerException is not deserialized and is null.
Is there something obvious I'm missing? I'm not sure why this would work in 10.0.3 and break in 11.0.2.
My exception classes –
public sealed class MyHttpException : MyBaseException
{
public MyHttpException(HttpStatusCode httpStatusCode, int MyStatusCode)
: base(MyStatusCode) => HttpStatusCode = httpStatusCode;
public MyHttpException(HttpStatusCode httpStatusCode, int MyStatusCode, string message)
: base(MyStatusCode, message) => HttpStatusCode = httpStatusCode;
public MyHttpException(HttpStatusCode httpStatusCode, int MyStatusCode, Exception innerException)
: base(MyStatusCode, innerException) => HttpStatusCode = httpStatusCode;
public MyHttpException(HttpStatusCode httpStatusCode, int MyStatusCode, string message, Exception innerException)
: base(MyStatusCode, message, innerException) => HttpStatusCode = httpStatusCode;
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
private MyHttpException(SerializationInfo info, StreamingContext context)
: base(info, context) => HttpStatusCode = (HttpStatusCode)info.GetValue("HttpStatusCode", typeof(HttpStatusCode));
public HttpStatusCode HttpStatusCode { get; set; }
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
info.AddValue("HttpStatusCode", HttpStatusCode);
// MUST call through to the base class to let it save its own state
base.GetObjectData(info, context);
}
}
public abstract class MyBaseException : Exception
{
public MyBaseException(int MyStatusCode) => this.MyStatusCode = MyStatusCode;
public MyBaseException(int MyStatusCode, string message)
: base(message) => this.MyStatusCode = MyStatusCode;
public MyBaseException(int MyStatusCode, Exception innerException)
: base("MyErrorCode: " + MyStatusCode + ". " + MyStatusCodes.GetDescription(MyStatusCode) + ". " + innerException.Message, innerException) => this.MyStatusCode = MyStatusCode;
public MyBaseException(int MyStatusCode, string message, Exception innerException)
: base(message, innerException) => this.MyStatusCode = MyStatusCode;
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
protected MyBaseException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
MyStatusCode = info.GetInt32("MyStatusCode");
}
public int MyStatusCode { get; set; }
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
info.AddValue("MyStatusCode", MyStatusCode);
// MUST call through to the base class to let it save its own state
base.GetObjectData(info, context);
}
}
Thanks
Upvotes: 3
Views: 4330
Reputation: 116876
In Json.NET 11 a change was made to how ISerializable
types are serialized. According to the release notes:
- Change - Types that implement ISerializable but don't have [SerializableAttribute] are not serialized using ISerializable
Thus you must now mark your exception types with SerializableAttribute
:
[Serializable]
public sealed class MyHttpException : MyBaseException
{
}
[Serializable]
public abstract class MyBaseException : Exception
{
}
Alternatively, you could create a custom contract resolver that restores the old behavior:
public class PreferISerializableContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
if (!IgnoreSerializableInterface
&& contract is JsonObjectContract
&& typeof(ISerializable).IsAssignableFrom(objectType)
&& !objectType.GetCustomAttributes(true).OfType<JsonContainerAttribute>().Any())
{
contract = CreateISerializableContract(objectType);
}
return contract;
}
}
(You may want to cache the contract resolver for best performance.)
Why was this change made? According to Issue #1622: classes deriving from System.Exception do not serialize/deserialize properly:
Json.NET previous wasn't serializing
ISerializable
types correctly. TheSerializableAttribute
is required.See here for more info dotnet/corefx#23415.
And in turn the linked issue dotnet/corefx Issue #23415: PlatformNotSupportedException when attempting to serialize DirectoryInfo instance with Newtonsoft.Json indicates that the change was made at the request of the .NET Core team:
JamesNK commented on Aug 29, 2017
So the issue is Json.NET is checking that a type implements ISerializable but not also checking for the SerialiazableAttribute?
ViktorHofer commented on Aug 29, 2017
Correct :)
Thus if you do use PreferISerializableContractResolver
instead of marking your ISerializable
types with [Serializable]
, you might encounter this very issue in .NET Core.
Upvotes: 4