c# - 如何使用 Newtonsoft JSON(反)序列化 XmlException?

标签 c# json json.net

此示例代码:

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

在 Newtonsoft.Json.dll 中抛出 InvalidCastException: Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'System.String' with 以下堆栈跟踪:
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 与 ISerializabe 之间的不兼容。 + SerializationInfo 最初设计用于 BinaryFormatter 其流是强类型的。 IE。 ISerializable 的实现有时期望序列化流包含序列化字段的完整类型信息。事实证明, XmlException 有一个这样的实现。

具体如下。当 Json.NET 去调用 serialization constructor对于 ISerializable类型,它 constructs a SerializationInfo 并通过 JsonFormatterConverter SerializationInfo.GetValue(String, Type) 时,应该处理从 JSON 数据转换为所需类型的工作叫做。现在,当找不到指定的值时,此方法会引发异常。而且,不幸的是,没有 SerializationInfo.TryGetValue()方法,要求需要反序列化可选字段的类使用 GetEnumerator() 手动循环遍历属性.但此外,没有方法可以检索构造函数中设置的转换器,这意味着在需要时无法转换可选字段,因此必须使用精确的预期类型反序列化需求。

您可以在 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在这一点上,所以反序列化窒息。

Json.NET 可以通过在 JsonSerializerInternalReader.CreateISerializable() 中解决这个特定问题, 替换字符串值 JValue构建其 SerializationInfo 时带有实际字符串的标记,然后重新转换为 JValueJsonFormatterConverter如果需要转换。但是,这不会解决此类问题。例如,当 int由 Json.NET 往返,它变成了 long , 如果没有转换就会抛出。当然还有 DateTime 字段将在不转换的情况下抛出。这也将是一个突破性的变化 ISerializable以前手工制作以与 Json.NET 一起使用的类可能会中断。

您可能会 report an issue关于这一点,但我怀疑它会很快得到修复。

解决该问题的更可靠方法是创建自定义 JsonConverter 嵌入了 ISerializable 的完整类型信息类型。

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

第一个最简单的解决方案是嵌入 BinaryFormatter流在你的 JSON 中。 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 的可读性在一定程度上减轻了安全隐患。您还可以创建一个 custom SerializationBinder 进一步减少安全隐患只加载预期类型,如 TypeNameHandling caution in Newtonsoft Json 中所述.

    我不确定在部分信任的情况下应该做什么。 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);
            }
    

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

    关于c# - 如何使用 Newtonsoft JSON(反)序列化 XmlException?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35015357/

    相关文章:

    c# - 在 XAML 中从 DependecyProperty 绑定(bind)到 DataContext (ViewModel)

    javascript - Javascript 中变量不断变成对象而不是数组

    javascript - 通过主键选择记录

    json - 如何屏蔽 JSON 中的敏感值以进行日志记录

    c# - 如何将roleclaim包含到accesstoken中?

    c# - 拆分 XML 文档,从重复元素创建多个输出文件

    c# - WP7 ListBox 选中的项目没有改变颜色

    java - 新行,双引号在 json 数据中给出错误

    c# - 传递 JSON,反序列化因反斜杠而失败

    c# - Newtonsoft JSON.NET 创建类型字典