JsonConvert.DeserializeXmlNode 中的 StackOverflowException

StackOverflowException in JsonConvert.DeserializeXmlNode

我们最近从 6.0.1 升级到 Json.NET 10.0r2,升级后我注意到我们的一个单元测试在尝试反序列化无效 Json 时抛出堆栈溢出异常。测试的目的是确保处理无效 Json。这个相同的测试曾经抛出 JsonSerializationException,但现在正在使用 Whosebug 关闭 nUnit。

我已经在 Json.NET 自己的单元测试项目中复制了这个测试:

[Test]
public void FailOnInvalidJSON( )
{
    string json = @"{'Row' : ";

    Assert.Throws<JsonSerializationException>(()=>JsonConvert.DeserializeXmlNode(json, "ROOT"));
}

有什么变通办法吗?

谢谢!

更新

并迅速 fixed in change set 822c3f0。应该在10.0.2之后的下一个版本中。

原答案

看起来像是对 JsonTextReader in version 8.0.1 may have uncovered a bug in XmlNodeConverter 的更改。

7.0.1, when the unexpected end of file is reached, JsonReader.TokenType becomes JsonToken.None after the next attempt to Read(), which causes DeserializeNode() to throw an Unexpected JsonToken when deserializing node: None exception. But in 8.0.1 及之后的 TokenType 似乎停留在最后遇到的标记的类型,即 JsonToken.PropertyName,这导致无限递归。

正确的解决方法是,在第 2171 行附近的 XmlNodeConverter.DeserializeNode() 中,从 reader.Read():

检查 return
case JsonToken.PropertyName:
    if (currentNode.NodeType == XmlNodeType.Document && document.DocumentElement != null)
    {
        throw JsonSerializationException.Create(reader, "JSON root object has multiple properties. The root object must have a single property in order to create a valid XML document. Consider specifying a DeserializeRootElementName.");
    }

    string propertyName = reader.Value.ToString();
    // Need to check the return from reader.Read() here:
    if (!reader.Read())
    {
        throw JsonSerializationException.Create(reader, "Unexpected end of file when deserializing property: " + propertyName );
    }

... XmlNodeConverter.cs where the return from reader.Read() needs to be checked, for instance in ReadAttributeElements(JsonReader reader, XmlNamespaceManager manager) around line 1942.

中似乎还有几个地方

如果你愿意,你可以report an issue

与此同时,您可以选择的解决方法是:

  • 以不同的方式破坏 JSON,例如:

    string json = @"{'Row' : }";
    

    并检查更一般的异常 JsonException

  • 将JSON预解析为JToken:

    Assert.Throws<JsonException>(()=>JsonConvert.DeserializeXmlNode(JToken.Parse(json).ToString(), "ROOT"));