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

时间:2016-01-26 13:50:19

标签: c# json json.net

此示例代码:

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

在Newtonsoft.Json.dll中抛出InvalidCastException:无法转换类型&#39; Newtonsoft.Json.Linq.JValue&#39;输入&#39; System.String&#39;使用以下堆栈跟踪:

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

创建了一个问题

1 个答案:

答案 0 :(得分:3)

问题

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

具体如下。当Json.NET为ISerializable类型调用serialization constructor时,它constructs a SerializationInfo并传递JsonFormatterConverter,它应该处理从JSON数据转换为所需类型的工作何时调用SerializationInfo.GetValue(String, Type)。现在,此方法在找不到指定值时抛出异常。而且,遗憾的是,没有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令牌,然后如果需要转换,稍后会在JValue中重新转换为JsonFormatterConverter。但是,这不会解决问题的类别。例如,当Json.NET对int进行往返时,它变为long,如果没有转换,则会抛出ISerializable。当然,DateTime字段会在没有转换的情况下抛出。这也是一个突破性的变化,以前手工制作与Json.NET一起工作的ISerializable类可能会破坏。

你可能report an issue对此有所怀疑,但我很怀疑它会很快得到解决。

解决问题的更有力的方法是创建一个自定义JsonConverter,其中嵌入了BinaryFormatter类型的完整类型信息。

解决方案1:嵌入二进制

第一个最简单的解决方案是在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>() } };

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

BinaryFormatter

缺点是:

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

  2. JSON完全不可读。

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

  4. 我相信JsonConverter只能完全信任。

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

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

    Json.NET还具有通过将JsonSerializer.TypeNameHandling设置为适当的值来为序列化流中的非基本类型嵌入.NET类型信息的可选功能。使用此功能以及原始类型的包装器,可以创建封装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; } }

    {
      "$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,如下所示:

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

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

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

    myView.setOnTouchListener(new OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {
            if(event.getAction() == MotionEvent.ACTION_DOWN){
                //do something when pressed down
    
                return true;
            }
            else if(event.getAction() == MotionEvent.ACTION_UP){
               //do something when let go
    
               return true;
            }   
            return false;
        }
    });
    

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