如何(de)使用Newtonsoft JSON序列化XmlException?

Fab*_*ers 6 c# json json.net

此示例代码:

var json = JsonConvert.SerializeObject(new XmlException("bla"));
var exception = JsonConvert.DeserializeObject<XmlException>(json);
Run Code Online (Sandbox Code Playgroud)

在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:\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()
Run Code Online (Sandbox Code Playgroud)

我错过了什么吗?

https://github.com/JamesNK/Newtonsoft.Json/issues/801上创建了一个问题

dbc*_*dbc 4

问题

\n\n

这里的基本问题是弱类型的 JSON 和最初设计用于强类型流的ISerializabe+之间的不兼容。即,有时期望序列化流包含序列化字段的完整类型信息。事实证明有一个这样的实现。SerializationInfoBinaryFormatterISerializableXmlException

\n\n

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

\n\n

您可以在构造函数的参考源中看到这一点XmlException

\n\n
    protected XmlException(SerializationInfo info, StreamingContext context) : base(info, context) {\n        res                 = (string)  info.GetValue("res"  , typeof(string));\n        args                = (string[])info.GetValue("args", typeof(string[]));\n        lineNumber          = (int)     info.GetValue("lineNumber", typeof(int));\n        linePosition        = (int)     info.GetValue("linePosition", typeof(int));\n\n        // deserialize optional members\n        sourceUri = string.Empty;\n        string version = null;\n        foreach ( SerializationEntry e in info ) {\n            switch ( e.Name ) {\n                case "sourceUri":\n                    sourceUri = (string)e.Value;\n                    break;\n                case "version":\n                    version = (string)e.Value;\n                    break;\n            }\n        }\n
Run Code Online (Sandbox Code Playgroud)\n\n

事实证明这e.Value仍然JValue不是一个string此时

\n\n

Json.NET 可以解决这个特定问题,方法是在 in构造其 时JsonSerializerInternalReader.CreateISerializable()将字符串值标记替换为实际字符串,然后在需要转换时重新转换为in 。然而,这并不能解决此类问题。例如,当 an被 Json.NET 往返时,它会变成 a ,如果在没有转换的情况下进行强制转换,则会抛出异常。当然,一个字段会在没有转换的情况下抛出。这也将是一个突破性的改变JValueSerializationInfoJValueJsonFormatterConverterintlongDateTimeISerializable以前手工制作的与 Json.NET 一起使用的类可能会被破坏。

\n\n

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

\n\n

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

\n\n

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

\n\n

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

\n\n
public class BinaryConverter<T> : JsonConverter where T : ISerializable\n{\n    class BinaryData\n    {\n        public byte[] binaryData { get; set; }\n    }\n\n    public override bool CanConvert(Type objectType)\n    {\n        return typeof(T).IsAssignableFrom(objectType);\n    }\n\n    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)\n    {\n        if (reader.TokenType == JsonToken.Null)\n            return null;\n        var data = serializer.Deserialize<BinaryData>(reader);\n        if (data == null || data.binaryData == null)\n            return null;\n        return BinaryFormatterHelper.FromByteArray<T>(data.binaryData);\n\n    }\n\n    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)\n    {\n        var data = new BinaryData { binaryData = BinaryFormatterHelper.ToByteArray(value) };\n        serializer.Serialize(writer, data);\n    }\n}\n\npublic static class BinaryFormatterHelper\n{\n    public static byte [] ToByteArray<T>(T obj)\n    {\n        using (var stream = new MemoryStream())\n        {\n            new BinaryFormatter().Serialize(stream, obj);\n            return stream.ToArray();\n        }\n    }\n\n    public static T FromByteArray<T>(byte[] data)\n    {\n        return FromByteArray<T>(data, null);\n    }\n\n    public static T FromByteArray<T>(byte[] data, BinaryFormatter formatter)\n    {\n        using (var stream = new MemoryStream(data))\n        {\n            formatter = (formatter ?? new BinaryFormatter());\n            var obj = formatter.Deserialize(stream);\n            if (obj is T)\n                return (T)obj;\n            return default(T);\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
var settings = new JsonSerializerSettings { Converters =  new[] { new BinaryConverter<Exception>() } };\n
Run Code Online (Sandbox Code Playgroud)\n\n

缺点是:

\n\n
    \n
  1. 反序列化不受信任的数据存在严重的安全隐患。由于类型信息完全嵌入到专有的、不可读的序列化流中,因此在完成此操作之前,您无法知道要构造什么。

  2. \n
  3. JSON 完全不可读。

  4. \n
  5. 我相信BinaryFormatter某些 .Net 版本中缺少此功能。

  6. \n
  7. 相信BinaryFormatter只有完全信任才能使用。

  8. \n
\n\n

但是,如果您想做的只是在您控制的进程之间序列化异常,那么这可能就足够了。

\n\n

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

\n\n

JsonSerializer.TypeNameHandlingJson.NET 还具有通过设置适当的值在序列化流中嵌入非基本类型的 .NET 类型信息的可选功能。使用此功能以及原始类型的包装器,可以创建JsonConverter封装SerializationInfoSerializationEntry包含所有已知类型信息的 :

\n\n
public class ISerializableConverter<T> : JsonConverter where T : ISerializable\n{\n    public override bool CanConvert(Type objectType)\n    {\n        return typeof(T).IsAssignableFrom(objectType);\n    }\n\n    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)\n    {\n        if (reader.TokenType == JsonToken.Null)\n            return null;\n        var oldTypeNameHandling = serializer.TypeNameHandling;\n        var oldAssemblyFormat = serializer.TypeNameAssemblyFormat;\n        try\n        {\n            if (serializer.TypeNameHandling == TypeNameHandling.None)\n                serializer.TypeNameHandling = TypeNameHandling.Auto;\n            else if (serializer.TypeNameHandling == TypeNameHandling.Arrays)\n                serializer.TypeNameHandling = TypeNameHandling.All;\n            var data = serializer.Deserialize<SerializableData>(reader);\n            var type = data.ObjectType;\n            var info = new SerializationInfo(type, new FormatterConverter());\n            foreach (var item in data.Values)\n                info.AddValue(item.Key, item.Value.ObjectValue, item.Value.ObjectType);\n            var value = Activator.CreateInstance(type, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { info, serializer.Context }, serializer.Culture);\n            if (value is IObjectReference)\n                value = ((IObjectReference)value).GetRealObject(serializer.Context);\n            return value;\n        }\n        finally\n        {\n            serializer.TypeNameHandling = oldTypeNameHandling;\n            serializer.TypeNameAssemblyFormat = oldAssemblyFormat;\n        }\n    }\n\n    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)\n    {\n        var oldTypeNameHandling = serializer.TypeNameHandling;\n        var oldAssemblyFormat = serializer.TypeNameAssemblyFormat;\n        try\n        {\n            var serializable = (ISerializable)value;\n            var context = serializer.Context;\n            var info = new SerializationInfo(value.GetType(), new FormatterConverter());\n            serializable.GetObjectData(info, context);\n            var data = SerializableData.CreateData(info, value.GetType());\n\n            if (serializer.TypeNameHandling == TypeNameHandling.None)\n                serializer.TypeNameHandling = TypeNameHandling.Auto;\n            else if (serializer.TypeNameHandling == TypeNameHandling.Arrays)\n                serializer.TypeNameHandling = TypeNameHandling.All;\n            // The following seems to be required by https://github.com/JamesNK/Newtonsoft.Json/issues/787\n            serializer.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Full;\n            serializer.Serialize(writer, data, typeof(SerializableData));\n        }\n        finally\n        {\n            serializer.TypeNameHandling = oldTypeNameHandling;\n            serializer.TypeNameAssemblyFormat = oldAssemblyFormat;\n        }\n    }\n}\n\nabstract class SerializableValue\n{\n    [JsonIgnore]\n    public abstract object ObjectValue { get; }\n\n    [JsonIgnore]\n    public abstract Type ObjectType { get; }\n\n    public static SerializableValue CreateValue(SerializationEntry entry)\n    {\n        return CreateValue(entry.ObjectType, entry.Value);\n    }\n\n    public static SerializableValue CreateValue(Type type, object value)\n    {\n        if (value == null)\n        {\n            if (type == null)\n                throw new ArgumentException("type and value are both null");\n            return (SerializableValue)Activator.CreateInstance(typeof(SerializableValue<>).MakeGenericType(type));\n        }\n        else\n        {\n            type = value.GetType(); // Use most derived type\n            return (SerializableValue)Activator.CreateInstance(typeof(SerializableValue<>).MakeGenericType(type), value);\n        }\n    }\n}\n\nsealed class SerializableValue<T> : SerializableValue\n{\n    public SerializableValue() : base() { }\n\n    public SerializableValue(T value)\n        : base()\n    {\n        this.Value = value;\n    }\n\n    public override object ObjectValue { get { return Value; } }\n\n    public override Type ObjectType { get { return typeof(T); } }\n\n    [JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)]\n    public T Value { get; private set; }\n}\n\nabstract class SerializableData\n{\n    public SerializableData()\n    {\n        this.Values = new Dictionary<string, SerializableValue>();\n    }\n\n    public SerializableData(IEnumerable<SerializationEntry> values)\n    {\n        this.Values = values.ToDictionary(v => v.Name, v => SerializableValue.CreateValue(v));\n    }\n\n    [JsonProperty("values", ItemTypeNameHandling = TypeNameHandling.Auto)]\n    public Dictionary<string, SerializableValue> Values { get; private set; }\n\n    [JsonIgnore]\n    public abstract Type ObjectType { get; }\n\n    public static SerializableData CreateData(SerializationInfo info, Type initialType)\n    {\n        if (info == null)\n            throw new ArgumentNullException("info");\n        var type = info.GetSavedType(initialType);\n        if (type == null)\n            throw new InvalidOperationException("type == null");\n        return (SerializableData)Activator.CreateInstance(typeof(SerializableData<>).MakeGenericType(type), info.AsEnumerable());\n    }\n}\n\nsealed class SerializableData<T> : SerializableData\n{\n    public SerializableData() : base() { }\n\n    public SerializableData(IEnumerable<SerializationEntry> values) : base(values) { }\n\n    public override Type ObjectType { get { return typeof(T); } }\n}\n\npublic static class SerializationInfoExtensions\n{\n    public static IEnumerable<SerializationEntry> AsEnumerable(this SerializationInfo info)\n    {\n        if (info == null)\n            throw new NullReferenceException();\n        var enumerator = info.GetEnumerator();\n        while (enumerator.MoveNext())\n        {\n            yield return enumerator.Current;\n        }\n    }\n\n    public static Type GetSavedType(this SerializationInfo info, Type initialType)\n    {\n        if (initialType != null)\n        {\n            if (info.FullTypeName == initialType.FullName\n                && info.AssemblyName == initialType.Module.Assembly.FullName)\n                return initialType;\n        }\n        var assembly = Assembly.Load(info.AssemblyName);\n        if (assembly != null)\n        {\n            var type = assembly.GetType(info.FullTypeName);\n            if (type != null)\n                return type;\n        }\n        return initialType;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后使用以下设置:

\n\n

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

\n\n
\n
{\n  "$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",\n  "values": {\n    "ClassName": {\n      "$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",\n      "value": "System.Xml.XmlException"\n    },\n    "Message": {\n      "$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",\n      "value": "bla"\n    },\n    "Data": {\n      "$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"\n    },\n    "InnerException": {\n      "$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"\n    },\n    "HelpURL": {\n      "$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"\n    },\n    "StackTraceString": {\n      "$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"\n    },\n    "RemoteStackTraceString": {\n      "$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"\n    },\n    "RemoteStackIndex": {\n      "$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",\n      "value": 0\n    },\n    "ExceptionMethod": {\n      "$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"\n    },\n    "HResult": {\n      "$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",\n      "value": -2146232000\n    },\n    "Source": {\n      "$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"\n    },\n    "res": {\n      "$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",\n      "value": "Xml_UserException"\n    },\n    "args": {\n      "$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",\n      "value": [\n        "bla"\n      ]\n    },\n    "lineNumber": {\n      "$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",\n      "value": 0\n    },\n    "linePosition": {\n      "$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",\n      "value": 0\n    },\n    "sourceUri": {\n      "$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"\n    },\n    "version": {\n      "$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",\n      "value": "2.0"\n    }\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

正如您所看到的,JSON 的可读性在一定程度上减轻了安全隐患。您还可以创建自定义SerializationBinder以进一步减少仅加载预期类型的​​安全隐患,如Newtonsoft Json 中的 TypeNameHandling 警告中所述。

\n\n

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

\n\n
    private object CreateISerializable(JsonReader reader, JsonISerializableContract contract, JsonProperty member, string id)\n    {\n        Type objectType = contract.UnderlyingType;\n\n        if (!JsonTypeReflector.FullyTrusted)\n        {\n            string message = @"Type \'{0}\' implements ISerializable but cannot be deserialized using