如何使用Newtonsoft JSON(反)序列化XmlException? [英] How to (de)serialize a XmlException with Newtonsoft JSON?

查看:223
本文介绍了如何使用Newtonsoft JSON(反)序列化XmlException?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此示例代码:

var json = JsonConvert.SerializeObject(new XmlException("bla"));
var exception = JsonConvert.DeserializeObject<XmlException>(json);

在Newtonsoft.Json.dll中引发InvalidCastException:无法将类型为"Newtonsoft.Json.Linq.JValue"的对象转换为具有以下堆栈跟踪的"System.String":

throws an InvalidCastException in Newtonsoft.Json.dll: Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'System.String' with the following stack trace:

at System.Xml.XmlException..ctor(SerializationInfo info, StreamingContext context)
at Void .ctor(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)(Object[] )
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateISerializable(JsonReader reader, JsonISerializableContract contract, JsonProperty member, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value)
at TestJson.Program.Main(String[] args) in C:\Projects\TestJson\TestJson\Program.cs:line 21
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

我想念什么吗?

已在 https://github.com/JamesNK/Newtonsoft创建问题. Json/issues/801

推荐答案

问题

这里的基本问题是弱类型的JSON与 SerializationInfo 最初旨在与 XmlException 具有一种这样的实现.

The basic problem here is an incompatibility between JSON, which is weakly typed, and ISerializabe + SerializationInfo which were originally designed to work with BinaryFormatter whose streams are strongly typed. I.e. implementations of ISerializable sometimes expect that the serialization stream contains the complete type information for serialized fields. And it turns out that XmlException has one such implementation.

具体如下.当Json.NET调用序列化构造器时,该类型,它构造SerializationInfo 并传递 ,当 GetEnumerator() .但是此外,没有方法可以检索构造函数中的转换器集,这意味着可选字段无法在需要时进行转换,因此必须已经使用预期类型精确地反序列化了需求.

The specifics are as follows. When Json.NET goes to call the serialization constructor for an ISerializable type, it constructs a SerializationInfo and passes a JsonFormatterConverter that should handle the job of converting from JSON data to the required type when SerializationInfo.GetValue(String, Type) is called. Now, this method throws an exception when the named value is not found. And, unfortunately, there is no SerializationInfo.TryGetValue() method, requiring classes that need to deserialize optional fields to loop through the properties manually with GetEnumerator(). But in addition, there is no method to retrieve the converter set in the constructor, meaning that optional fields cannot be converted when required so must needs have been deserialized with precisely the expected type.

您可以在参考源中看到此内容. XmlException 的构造函数:

You can see this in the reference source for the constructor of XmlException:

    protected XmlException(SerializationInfo info, StreamingContext context) : base(info, context) {
        res                 = (string)  info.GetValue("res"  , typeof(string));
        args                = (string[])info.GetValue("args", typeof(string[]));
        lineNumber          = (int)     info.GetValue("lineNumber", typeof(int));
        linePosition        = (int)     info.GetValue("linePosition", typeof(int));

        // deserialize optional members
        sourceUri = string.Empty;
        string version = null;
        foreach ( SerializationEntry e in info ) {
            switch ( e.Name ) {
                case "sourceUri":
                    sourceUri = (string)e.Value;
                    break;
                case "version":
                    version = (string)e.Value;
                    break;
            }
        }

事实证明,e.Value仍然是 JValue 目前还不是string,因此反序列化会造成窒息.

It turns out that e.Value is still a JValue not a string at this point, so deserialization chokes.

Json.NET可以通过 DateTime 字段将不进行转换而抛出.以前手工制作的可与Json.NET配合使用的ISerializable类也可能会发生重大变化.

Json.NET could fix this specific problem by, in JsonSerializerInternalReader.CreateISerializable(), replacing string-valued JValue tokens with actual strings when constructing its SerializationInfo, then later re-converting to JValue in JsonFormatterConverter if conversion is necessary. However, this would not fix this category of problems. For instance, when an int is round-tripped by Json.NET it becomes a long, which will throw if cast without conversion. And of course a DateTime field will throw without conversion. It would also be a breaking change in that ISerializable classes that had previously been hand-crafted to work with Json.NET could break.

您可能报告有关此问题,但我对此表示怀疑它将很快得到修复.

You might report an issue about this, but I'm skeptical it will get fixed any time soon.

解决此问题的更强大的方法是创建自定义 JsonConverter 嵌入了ISerializable类型的完整类型信息.

A more robust approach to solving the problem would be to create a custom JsonConverter that embeds complete type information for ISerializable types.

解决方案1:嵌入二进制

第一个最简单的解决方案是将BinaryFormatter流嵌入到JSON中. Exception类的序列化代码最初设计为与BinaryFormatter兼容,因此应该相当可靠:

The first, simplest solution would be to embed a BinaryFormatter stream inside your JSON. The serialization code for the Exception classes was originally designed to be compatible with BinaryFormatter, so this should be fairly reliable:

public class BinaryConverter<T> : JsonConverter where T : ISerializable
{
    class BinaryData
    {
        public byte[] binaryData { get; set; }
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var data = serializer.Deserialize<BinaryData>(reader);
        if (data == null || data.binaryData == null)
            return null;
        return BinaryFormatterHelper.FromByteArray<T>(data.binaryData);

    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var data = new BinaryData { binaryData = BinaryFormatterHelper.ToByteArray(value) };
        serializer.Serialize(writer, data);
    }
}

public static class BinaryFormatterHelper
{
    public static byte [] ToByteArray<T>(T obj)
    {
        using (var stream = new MemoryStream())
        {
            new BinaryFormatter().Serialize(stream, obj);
            return stream.ToArray();
        }
    }

    public static T FromByteArray<T>(byte[] data)
    {
        return FromByteArray<T>(data, null);
    }

    public static T FromByteArray<T>(byte[] data, BinaryFormatter formatter)
    {
        using (var stream = new MemoryStream(data))
        {
            formatter = (formatter ?? new BinaryFormatter());
            var obj = formatter.Deserialize(stream);
            if (obj is T)
                return (T)obj;
            return default(T);
        }
    }
}

然后使用以下设置进行序列化:

And then serialize with the following settings:

var settings = new JsonSerializerSettings { Converters =  new[] { new BinaryConverter<Exception>() } };

缺点是:

  1. 反序列化不受信任的数据存在严重的安全隐患.由于类型信息已完全嵌入专有的,不可读的序列化流中,因此您不知道要构造什么.

  1. There is a severe security hazard deserializing untrusted data. Since the type information is completely embedded inside the proprietary, unreadable serialization stream, you cannot know what you are going to construct until you have done so.

JSON完全不可读.

The JSON is completely unreadable.

我相信某些.Net版本中缺少BinaryFormatter.

I believe BinaryFormatter is missing on some .Net versions.

我相信BinaryFormatter仅可在完全信任的情况下使用.

I believe BinaryFormatter can only be used in full trust.

但是,如果您要做的只是在您控制的进程之间序列化一个异常,这可能就足够了.

But if all you are trying to do is to serialize an exception between processes under your control, this might be good enough.

解决方案2:使用 TypeNameHandling 嵌入类型信息a> .

通过设置

Json.NET also has the optional ability to embed .NET type information for non-primitive types in a serialization stream, by setting JsonSerializer.TypeNameHandling to an appropriate value. Using this ability along with wrappers for primitive types, it's possible to create a JsonConverter that encapsulates SerializationInfo and SerializationEntry and contains all known type information:

public class ISerializableConverter<T> : JsonConverter where T : ISerializable
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var oldTypeNameHandling = serializer.TypeNameHandling;
        var oldAssemblyFormat = serializer.TypeNameAssemblyFormat;
        try
        {
            if (serializer.TypeNameHandling == TypeNameHandling.None)
                serializer.TypeNameHandling = TypeNameHandling.Auto;
            else if (serializer.TypeNameHandling == TypeNameHandling.Arrays)
                serializer.TypeNameHandling = TypeNameHandling.All;
            var data = serializer.Deserialize<SerializableData>(reader);
            var type = data.ObjectType;
            var info = new SerializationInfo(type, new FormatterConverter());
            foreach (var item in data.Values)
                info.AddValue(item.Key, item.Value.ObjectValue, item.Value.ObjectType);
            var value = Activator.CreateInstance(type, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { info, serializer.Context }, serializer.Culture);
            if (value is IObjectReference)
                value = ((IObjectReference)value).GetRealObject(serializer.Context);
            return value;
        }
        finally
        {
            serializer.TypeNameHandling = oldTypeNameHandling;
            serializer.TypeNameAssemblyFormat = oldAssemblyFormat;
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var oldTypeNameHandling = serializer.TypeNameHandling;
        var oldAssemblyFormat = serializer.TypeNameAssemblyFormat;
        try
        {
            var serializable = (ISerializable)value;
            var context = serializer.Context;
            var info = new SerializationInfo(value.GetType(), new FormatterConverter());
            serializable.GetObjectData(info, context);
            var data = SerializableData.CreateData(info, value.GetType());

            if (serializer.TypeNameHandling == TypeNameHandling.None)
                serializer.TypeNameHandling = TypeNameHandling.Auto;
            else if (serializer.TypeNameHandling == TypeNameHandling.Arrays)
                serializer.TypeNameHandling = TypeNameHandling.All;
            // The following seems to be required by https://github.com/JamesNK/Newtonsoft.Json/issues/787
            serializer.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Full;
            serializer.Serialize(writer, data, typeof(SerializableData));
        }
        finally
        {
            serializer.TypeNameHandling = oldTypeNameHandling;
            serializer.TypeNameAssemblyFormat = oldAssemblyFormat;
        }
    }
}

abstract class SerializableValue
{
    [JsonIgnore]
    public abstract object ObjectValue { get; }

    [JsonIgnore]
    public abstract Type ObjectType { get; }

    public static SerializableValue CreateValue(SerializationEntry entry)
    {
        return CreateValue(entry.ObjectType, entry.Value);
    }

    public static SerializableValue CreateValue(Type type, object value)
    {
        if (value == null)
        {
            if (type == null)
                throw new ArgumentException("type and value are both null");
            return (SerializableValue)Activator.CreateInstance(typeof(SerializableValue<>).MakeGenericType(type));
        }
        else
        {
            type = value.GetType(); // Use most derived type
            return (SerializableValue)Activator.CreateInstance(typeof(SerializableValue<>).MakeGenericType(type), value);
        }
    }
}

sealed class SerializableValue<T> : SerializableValue
{
    public SerializableValue() : base() { }

    public SerializableValue(T value)
        : base()
    {
        this.Value = value;
    }

    public override object ObjectValue { get { return Value; } }

    public override Type ObjectType { get { return typeof(T); } }

    [JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)]
    public T Value { get; private set; }
}

abstract class SerializableData
{
    public SerializableData()
    {
        this.Values = new Dictionary<string, SerializableValue>();
    }

    public SerializableData(IEnumerable<SerializationEntry> values)
    {
        this.Values = values.ToDictionary(v => v.Name, v => SerializableValue.CreateValue(v));
    }

    [JsonProperty("values", ItemTypeNameHandling = TypeNameHandling.Auto)]
    public Dictionary<string, SerializableValue> Values { get; private set; }

    [JsonIgnore]
    public abstract Type ObjectType { get; }

    public static SerializableData CreateData(SerializationInfo info, Type initialType)
    {
        if (info == null)
            throw new ArgumentNullException("info");
        var type = info.GetSavedType(initialType);
        if (type == null)
            throw new InvalidOperationException("type == null");
        return (SerializableData)Activator.CreateInstance(typeof(SerializableData<>).MakeGenericType(type), info.AsEnumerable());
    }
}

sealed class SerializableData<T> : SerializableData
{
    public SerializableData() : base() { }

    public SerializableData(IEnumerable<SerializationEntry> values) : base(values) { }

    public override Type ObjectType { get { return typeof(T); } }
}

public static class SerializationInfoExtensions
{
    public static IEnumerable<SerializationEntry> AsEnumerable(this SerializationInfo info)
    {
        if (info == null)
            throw new NullReferenceException();
        var enumerator = info.GetEnumerator();
        while (enumerator.MoveNext())
        {
            yield return enumerator.Current;
        }
    }

    public static Type GetSavedType(this SerializationInfo info, Type initialType)
    {
        if (initialType != null)
        {
            if (info.FullTypeName == initialType.FullName
                && info.AssemblyName == initialType.Module.Assembly.FullName)
                return initialType;
        }
        var assembly = Assembly.Load(info.AssemblyName);
        if (assembly != null)
        {
            var type = assembly.GetType(info.FullTypeName);
            if (type != null)
                return type;
        }
        return initialType;
    }
}

然后使用以下设置:

这将生成如下所示的半可读JSON:

This produces semi-readable JSON like the following:

{
  "$type": "Question35015357.SerializableData`1[[System.Xml.XmlException, System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
  "values": {
    "ClassName": {
      "$type": "Question35015357.SerializableValue`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "value": "System.Xml.XmlException"
    },
    "Message": {
      "$type": "Question35015357.SerializableValue`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "value": "bla"
    },
    "Data": {
      "$type": "Question35015357.SerializableValue`1[[System.Collections.IDictionary, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    },
    "InnerException": {
      "$type": "Question35015357.SerializableValue`1[[System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    },
    "HelpURL": {
      "$type": "Question35015357.SerializableValue`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    },
    "StackTraceString": {
      "$type": "Question35015357.SerializableValue`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    },
    "RemoteStackTraceString": {
      "$type": "Question35015357.SerializableValue`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    },
    "RemoteStackIndex": {
      "$type": "Question35015357.SerializableValue`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "value": 0
    },
    "ExceptionMethod": {
      "$type": "Question35015357.SerializableValue`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    },
    "HResult": {
      "$type": "Question35015357.SerializableValue`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "value": -2146232000
    },
    "Source": {
      "$type": "Question35015357.SerializableValue`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    },
    "res": {
      "$type": "Question35015357.SerializableValue`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "value": "Xml_UserException"
    },
    "args": {
      "$type": "Question35015357.SerializableValue`1[[System.String[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "value": [
        "bla"
      ]
    },
    "lineNumber": {
      "$type": "Question35015357.SerializableValue`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "value": 0
    },
    "linePosition": {
      "$type": "Question35015357.SerializableValue`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "value": 0
    },
    "sourceUri": {
      "$type": "Question35015357.SerializableValue`1[[System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    },
    "version": {
      "$type": "Question35015357.SerializableValue`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "value": "2.0"
    }
  }
}

如您所见,JSON的可读性在某种程度上减轻了安全隐患.您还可以创建自定义SerializationBinder 来进一步降低安全性如 Newtonsoft Json中的TypeNameHanding警告 中所述,仅危险加载预期类型.

As you can see, the security hazard is somewhat mitigated by the readability of the JSON. You could also create a custom SerializationBinder to further reduce the security hazard loading only expected types as explained in TypeNameHandling caution in Newtonsoft Json.

我不确定在部分信任的情况下应该做什么. JsonSerializerInternalReader.CreateISerializable() 抛出部分信任:

I'm not sure what should be done in partial trust situations. JsonSerializerInternalReader.CreateISerializable() throws in partial trust:

    private object CreateISerializable(JsonReader reader, JsonISerializableContract contract, JsonProperty member, string id)
    {
        Type objectType = contract.UnderlyingType;

        if (!JsonTypeReflector.FullyTrusted)
        {
            string message = @"Type '{0}' implements ISerializable but cannot be deserialized using the ISerializable interface because the current application is not fully trusted and ISerializable can expose secure data." + Environment.NewLine +
                             @"To fix this error either change the environment to be fully trusted, change the application to not deserialize the type, add JsonObjectAttribute to the type or change the JsonSerializer setting ContractResolver to use a new DefaultContractResolver with IgnoreSerializableInterface set to true." + Environment.NewLine;
            message = message.FormatWith(CultureInfo.InvariantCulture, objectType);

            throw JsonSerializationException.Create(reader, message);
        }

所以转换器也应该如此.

So perhaps the converter should as well.

这篇关于如何使用Newtonsoft JSON(反)序列化XmlException?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆