JamesNK / Newtonsoft.Json

Json.NET is a popular high-performance JSON framework for .NET
https://www.newtonsoft.com/json
MIT License
10.71k stars 3.24k forks source link

Deserializing JSON to XmlDocument fails depending on member order #2875

Open theonlypwner opened 1 year ago

theonlypwner commented 1 year ago

Source/destination types

System.Xml.XmlDocument

Source/destination JSON

Example XML:

<?xml version="1.0" encoding="UTF-8"?><root></root>

JSON 1:

{"?xml":{"@version":"1.0","@encoding":"UTF-8"},"root":""}

JSON 2:

{"root":"","?xml":{"@version":"1.0","@encoding":"UTF-8"}}

Expected behavior

JSON 1 and JSON 2 should deserialize to the same XmlDocument.

Actual behavior

JSON 1 works.

JSON 2 fails. Some other programming languages randomize the order when serializing to JSON, which was then sent to C# server code using this library, resulting in this bug.

Newtonsoft.Json.JsonSerializationException: '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. Path '?xml', line 1, position 18.'

Steps to reproduce

Console.WriteLine(JsonConvert.DeserializeObject<XmlDocument>("{\"?xml\":{\"@version\":\"1.0\",\"@encoding\":\"UTF-8\"},\"root\":\"\"}").OuterXml);
// <?xml version="1.0" encoding="UTF-8"?><root></root>
Console.WriteLine(JsonConvert.DeserializeObject<XmlDocument>("{\"root\":\"\",\"?xml\":{\"@version\":\"1.0\",\"@encoding\":\"UTF-8\"}}").OuterXml);
// JsonSerializationException

The bug is in this check:

https://github.com/JamesNK/Newtonsoft.Json/blob/0a2e291c0d9c0c7675d445703e51750363a549ef/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs#L2087-L2090

It should still call DeserializeValue, which calls CreateInstruction, which calls document.CreateXmlDeclaration. However, it should call PrependChild instead of AppendChild.

Consider the example at https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmldocument.createxmldeclaration?view=net-7.0#examples

using System;
using System.IO;
using System.Xml;

public class Sample
{
  public static void Main()
  {
    XmlDocument doc = new XmlDocument();
    doc.LoadXml("<book genre='novel' ISBN='1-861001-57-5'>" +
                "<title>Pride And Prejudice</title>" +
                "</book>");

    //Create an XML declaration.
    XmlDeclaration xmldecl;
    xmldecl = doc.CreateXmlDeclaration("1.0",null,null);

    //Add the new node to the document.
    XmlElement root = doc.DocumentElement;
    doc.InsertBefore(xmldecl, root);

    Console.WriteLine("Display the modified XML...");
    doc.Save(Console.Out);
  }
}