如何使用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':

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:ProjectsTestJsonTestJsonProgram.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与ISerializabe + SerializationInfo之间的不兼容性,它们最初被设计为与强类型的BinaryFormatter一起工作。 即ISerializable实现有时会期望序列化流包含序列化字段的完整类型信息。 事实证明, XmlException有一个这样的实现。

具体如下。 当Json.NET去调用ISerializable类型的序列化构造函数时,它构造一个SerializationInfo并传递一个JsonFormatterConverter ,当调用SerializationInfo.GetValue(String, Type)时,该JsonFormatterConverter应该处理从JSON数据转换为所需类型的工作。 现在,当找不到指定的值时,此方法会引发异常。 不幸的是,没有SerializationInfo.TryGetValue()方法,需要需要反序列化可选字段的类,以便使用GetEnumerator()手动循环访问属性。 但是,另外,没有办法在构造函数中检索转换器集合,这意味着可选字段在需要时无法转换,因此必须使用预期的类型进行反序列化。

你可以在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 ,所以反序列化扼流圈。

JsonSerializerInternalReader.CreateISerializable() ,Json.NET可以通过在构造SerializationInfo时用实际字符串替换字符串值的JValue标记,然后在需要转换时在JsonFormatterConverter重新转换为JValue来解决此特定问题。 但是,这不能解决这类问题。 例如,当一个int被Json.NET往返时,它会变long ,如果在没有转换的情况下投射,则会抛出。 当然,一个DateTime字段将抛出而不转换。 这也将是一个突破性的变化,之前与Json.NET协同工作的ISerializable类可能会崩溃。

你可能会报告这个问题,但我怀疑它会很快得到解决。

解决这个问题的更健壮的方法是创建一个自定义的JsonConverter ,嵌入ISerializable类型的完整类型信息。

解决方案1:嵌入二进制文件

第一个最简单的解决方案是在您的JSON中嵌入一个BinaryFormatter流。 Exception类的序列化代码最初设计为与BinaryFormatter兼容,所以这应该是相当可靠的:

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);
        }
    }
}

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

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

缺点是:

  • 对不受信任的数据进行反序列化存在严重的安全隐患。 由于类型信息完全嵌入专有的,不可读的序列化流中,因此在完成之前无法知道要构建的内容。

  • JSON是完全不可读的。

  • 我相信BinaryFormatter在某些.Net版本中缺失。

  • 我相信BinaryFormatter只能用于完全信任。

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

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

    通过将JsonSerializer.TypeNameHandling设置为适当的值,Json.NET还具有可选功能,可以在序列化流中嵌入非基元类型的.NET类型信息。 使用这种能力和原始类型的包装,可以创建一个JsonConverter来封装SerializationInfoSerializationEntry并包含所有已知的类型信息:

    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:

    {
      "$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中的TypeNameHandling注意事项中所解释的。

    我不确定在部分信任情况下应该做什么。 JsonSerializerInternalReader.CreateISerializable()引发部分信任:

        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);
            }
    

    所以也许转换器也应该如此。

    链接地址: http://www.djcxy.com/p/31791.html

    上一篇: How to (de)serialize a XmlException with Newtonsoft JSON?

    下一篇: Animated page transitions in react