Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
264 views
in Technique[技术] by (71.8m points)

c# - Deserializing custom exceptions in Newtonsoft.Json

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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. The SerializableAttribute 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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...