Convert JSON to XML in C#
This is the code for the options you picked (NuGet package Newtonsoft.Json). It gives the same text as the page, except that on Windows the indented output uses \r\n:
System.Text.Json has no XML support, so there is no built-in equivalent. doc.Save to a StringWriter always writes an encoding="utf-16" declaration, because a .NET string is UTF-16; OuterXml keeps the declaration from the JSON, if any.
How XmlNodeConverter maps JSON to XML
| JSON | XML |
|---|---|
"name": "Ada" | <name>Ada</name> |
"@id": "1" | attribute id="1", only inside an element |
"#text", "#cdata-section" | text, <![CDATA[...]]> |
"?target": "data", "?xml": {"@version": "1.0"} | a processing instruction, the XML declaration |
"!DOCTYPE": {"@name": "a", "@system": "a.dtd"} | <!DOCTYPE a SYSTEM "a.dtd">; @internalSubset is ignored |
"item": [1, 2] | repeated elements: <item>1</item><item>2</item> |
"item": [] | nothing at all |
"a": [[1, 2], [3]] | a nested array becomes an element named after the property: <a><a>1</a><a>2</a></a><a><a>3</a></a> |
null, {} | <a /> |
"" | <a></a> |
"$id", "$ref", "$type", "$value" | json:id style attributes, with xmlns:json="http://james.newtonking.com/projects/json" added |
"@xmlns:p": "urn:p", "p:item" | a namespace declaration, an element in that namespace |
"#comment": "x" | not a comment: an element <_x0023_comment>. JSON comments (/* x */) become XML comments. |
A name that is not a valid XML name, "first name" | encoded with XmlConvert.EncodeName: <first_x0020_name> |
One root property, or deserializeRootElementName
An XML document has exactly one root element, and every property of the JSON root object becomes an element. {"customer": "Ada", "items": [...]} throws "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." Pass a root name (load "Several roots" above) and the whole object goes inside <root>. A root array needs it too: {"item": [1, 2]} would make two root elements, and throws "This document already has a 'DocumentElement' node." So do @ attributes at the top level. JSON that starts with an array or a value throws "XmlNodeConverter can only convert JSON that begins with an object."
Arrays, and writeArrayAttribute
A JSON array becomes repeated elements, so a one-item array and a single value give the same XML, and the way back through SerializeXmlNode loses the array. writeArrayAttribute: true marks one-item arrays with json:Array="true" so SerializeXmlNode writes an array again. Arrays of two or more items need no marker.
Numbers, booleans and dates: what comes out
The JSON is read by Newtonsoft's JsonTextReader, and each value is written back with XmlConvert, so the XML text is not always the JSON text:
- Numbers:
1.0becomes1,1e21becomes1E+21,0.00001becomes1E-05,-0.0becomes-0, andNaN,InfinitybecomeNaN,INF. The reader also takes hexadecimal (0x1Fis31) and octal:012is10, and08throws. Integers too big for alongare kept exactly. - Booleans are
trueandfalse. - Dates: by default (
DateParseHandling.DateTime) every string of the formyyyy-MM-ddTHH:mm:ss(with optional fraction and zone) or/Date(ticks)/is parsed into aDateTimeand written back withXmlConvert.ToString:2026-03-01T09:30:00.000Zbecomes2026-03-01T09:30:00Z: zero fractions go.2026-03-02T14:00:00+02:00is converted to the local time zone of the machine running the code:2026-03-02T12:00:00+00:00on a UTC server,2026-03-02T07:00:00-05:00in New York. The same JSON gives different XML on different servers./Date(1772352000000)/becomes2026-03-01T08:00:00Z; with an offset,/Date(...+0100)/, it is converted to local time too.2026-03-03T08:15:00(no zone) and date-only strings such as2026-03-04are unchanged.- A string such as
"2026-12-31T24:00:00"is read as midnight the next day; at the end of year 9999 it throwsArgumentOutOfRangeException.
To keep the text exactly as written, untick "Parse dates": DeserializeXmlNode itself has no setting for it, so the snippet switches to DeserializeObject<XmlDocument> with a JsonSerializerSettings { DateParseHandling = DateParseHandling.None } and an XmlNodeConverter carrying the same options. The page lists every string it rewrote.
encodeSpecialCharacters
With encodeSpecialCharacters: true, names are taken literally: @, #, $, ? and : are encoded (_x0040_id, p_x003A_item) instead of making attributes, text, namespaces or processing instructions. Use it when the JSON is data whose keys happen to start with those characters.
Namespaces
Declare a prefix with "@xmlns:p" before using it. A prefix with no declaration in scope is dropped when the XML is written: {"p:item": 1} gives <item>1</item>. A default namespace from "@xmlns" is inherited by child elements, like in XML.
FAQ
Why does DeserializeXmlNode say the root object must have a single property?
Each property of the JSON root becomes an element, and an XML document can have one root element. Pass deserializeRootElementName, for example "root".
Why did my dates change?
Newtonsoft parsed them as DateTime and wrote them in XML Schema format, in the server's time zone. Use the DateParseHandling.None variant shown in the snippet.
Why did DeserializeXmlNode change my dates?
Newtonsoft's reader parses ISO 8601 strings as DateTime by default (DateParseHandling.DateTime). The converter then writes them with XmlConvert: trailing zero fractions are dropped and offsets are converted to the server's local time zone. Deserialize with a JsonSerializerSettings whose DateParseHandling is None, and an XmlNodeConverter in its Converters, to keep the text.
Why does DeserializeXmlNode return null?
The JSON is empty or null. doc.OuterXml then throws a NullReferenceException, so check for null first.
Can System.Text.Json convert JSON to XML?
No. It has no XML support. Use Newtonsoft.Json for the conversion, or read the JSON with JsonDocument and write the XML with XmlWriter or XElement yourself, which avoids the date and array surprises.
Is my JSON uploaded?
No. The converter runs in your browser.