Skip to main content

JSON to XML convertor

Table of Contents

Introduction
#

This script is a Rhino-compatible JavaScript library that converts JSON data structures to XML format, designed to work with Rhino 1.7R5 and later. It provides a jsonToXml function that accepts JSON input (objects, arrays, or primitives) and extensive options for customizing the output XML, including root element naming, array item tags, attribute prefixes, pretty printing with configurable indentation, and the ability to wrap simple values in custom tags. The script handles XML escaping for special characters, sanitizes element names to comply with XML standards, separates JSON properties with attribute prefixes (default @) into actual XML attributes, and converts nested objects into child XML elements with configurable attribute node names.

Core function
#

jsonToXml(json, options) → string`</mark>

Parameters:

  • json (Object|Array|string|number|boolean|null) – Input data to convert
  • options (Object, optional) – Conversion options

Returns: (string) XML string representation


Supported Input Types
#

Input Type Example Output
Simple object {name: "Keith Armstrong", age: 35} <person><name>Keith Armstrong</name><age>35</age></person>
Array of primitives ["red", "green", "blue"] <palette><color>red</color><color>green</color>...</palette>
Nested objects {address: {city: "SF"}} <root><address><city>SF</city></address></root>
Arrays of objects [{id: 1}, {id: 2}] <orders><order><id>1</id></order>...</orders>
Primitive values "Hello" <message>Hello</message>
Empty arrays/null {items: [], count: null} <dataset><items></items><count></count></dataset>

Configuration Options
#

Option Type Default Description
rootName string 'root' Name for the root XML element
itemName string 'item' Name for array item elements
attrPrefix string '@' Prefix for JSON keys to treat as XML attributes
attrNodeName string 'attr' Element name for nested object properties
attrKeyName string 'attr-name' Attribute name for the key in nested property elements
prettyPrint boolean false Enable indentation and line breaks
indentChar string ' ' Indentation string (e.g., ' ')
wrapSimpleValues string 'value' Tag name to wrap simple values. If empty, simple values are not wrapped
attrForAllKeys boolean false Apply attrNodeName and attrKeyName to all top-level JSON keys

Features
#

  • XML Escaping Automatically escapes special characters (&, <, >, ", ') in values.
  • Attribute Support JSON keys prefixed with @ (configurable) become XML attributes: {"@id": "123", name: "Test"} → Test
  • Nested Object Handling Nested objects are converted to child elements, with their properties as value elements by default.
  • Element Name Sanitization Invalid XML characters in names are replaced with _, and names starting with numbers/special chars are prefixed with _.
  • Pretty Printing Optional formatted output with configurable indentation.
  • Rhino Compatibility Designed to work with Rhino 1.7R5+, using print() for output.

XML Escaping
#

Automatically escapes special characters in values:

  • & → &amp;
  • < → &lt;
  • > → &gt;
  • " → &quot;
  • ' → &apos;

Attribute Support
#

JSON keys prefixed with @ (configurable via attrPrefix) become XML attributes:

// Input
{ "@id": "P1001", "@category": "electronics", name: "Smartphone X" }

// Output with attrPrefix: '@'
<product id="P1001" category="electronics">
  <name>Smartphone X</name>
</product>

Nested Object Handling
#

Nested objects are converted to child elements. Their properties become:

<attr attr-name="key">value</attr>

unless configured otherwise via attrNodeName and attrKeyName.

Element Name Sanitization
#

  • Invalid XML characters ([^a-zA-Z0-9_:.-]) replaced with _
  • Names starting with numbers or special chars prefixed with _

Pretty Printing
#

Optional formatted output with configurable indentation:

<root>
  <name>Value</name>
  <nested>
    <item>Content</item>
  </nested>
</root>

Rhino compatibility
#

Designed to work with Rhino 1.7R5+.

Examples Included
#

The file contains 14 usage examples demonstrating:

  • Simple and complex objects
  • Arrays (primitives & objects)
  • XML attributes via @ prefix
  • Custom indentation
  • Empty arrays and null values
  • Special character escaping
  • Custom attribute element names (attrNodeName, attrKeyName)
  • Mixed content

Example 1: Simple Object
#

JSON: {
  "name": "Sarah Johnson",
  "age": 28,
  "city": "New York"
}
XML: <person><name>Sarah Johnson</name><age>28</age><city>New York</city></person>

Example 2: Array of Primitives
#

JSON: [
  "red",
  "green",
  "blue"
]
XML: <palette>
  <color>red</color>
  <color>green</color>
  <color>blue</color>
</palette>

Example 3: Nested Objects
#

JSON: {
  "name": "TechCorp",
  "founded": 2010,
  "address": {
    "street": "123 Innovation Blvd",
    "city": "San Francisco",
    "zip": "94105"
  }
}
XML: <company>
  <name>TechCorp</name>
  <founded>2010</founded>
  <address>
    <attr attr-name="street">123 Innovation Blvd</attr>
    <attr attr-name="city">San Francisco</attr>
    <attr attr-name="zip">94105</attr>
  </address>
</company>

Example 4: Object with Attributes
#

JSON: {
  "@id": "P1001",
  "@category": "electronics",
  "name": "Smartphone X",
  "price": 599.99,
  "inStock": true
}
XML: <product id="P1001" category="electronics">
  <name>Smartphone X</name>
  <price>599.99</price>
  <inStock>true</inStock>
</product>

Example 5: Complex Structure
#

JSON: {
  "@name": "Central Library",
  "books": [
    {
      "@isbn": "978-0134853987",
      "title": "The Pragmatic Programmer",
      "author": "Andrew Hunt",
      "year": 1999
    },
    {
      "@isbn": "978-0596517748",
      "title": "JavaScript: The Good Parts",
      "author": "Douglas Crockford",
      "year": 2008
    }
  ]
}
XML: <library name="Central Library">
  <books>
    <book isbn="978-0134853987">
      <title>The Pragmatic Programmer</title>
      <author>Andrew Hunt</author>
      <year>1999</year>
    </book>
    <book isbn="978-0596517748">
      <title>JavaScript: The Good Parts</title>
      <author>Douglas Crockford</author>
      <year>2008</year>
    </book>
  </books>
</library>

Example 6: XML Escaping
#

JSON: {
  "title": "Great Product <test>",
  "rating": 5,
  "comment": "This works perfectly! 5 < 10 & 3 > 2"
}
XML: <review>
  <title>Great Product &lt;test&gt;</title>
  <rating>5</rating>
  <comment>This works perfectly! 5 &lt; 10 &amp; 3 &gt; 2</comment>
</review>

Example 7: Array of Objects
#

JSON: [
  {
    "id": "A001",
    "amount": 150.5,
    "status": "shipped"
  },
  {
    "id": "A002",
    "amount": 275.25,
    "status": "processing"
  },
  {
    "id": "A003",
    "amount": 99.99,
    "status": "delivered"
  }
]
XML: <orders>
  <order>
    <id>A001</id>
    <amount>150.5</amount>
    <status>shipped</status>
  </order>
  <order>
    <id>A002</id>
    <amount>275.25</amount>
    <status>processing</status>
  </order>
  <order>
    <id>A003</id>
    <amount>99.99</amount>
    <status>delivered</status>
  </order>
</orders>

Example 8: Empty & Null Values
#

JSON: {
  "items": [],
  "count": null,
  "active": false
}
XML: <dataset>
  <items/>
  <count/>
  <active>false</active>
</dataset>

Example 9: Custom Indentation
#

JSON: {
  "setting1": "value1",
  "setting2": "value2",
  "nested": {
    "optionA": true,
    "optionB": false
  }
}
XML: <configuration>
    <setting1>value1</setting1>
    <setting2>value2</setting2>
    <nested>
        <attr attr-name="optionA">true</attr>
        <attr attr-name="optionB">false</attr>
    </nested>
</configuration>

Example 10: Example with attr-name default
#

JSON: {
  "setting1": "value1",
  "setting2": "value2",
  "nested": {
    "optionA": true,
    "optionB": false
  }
}
XML: <configuration>
    <setting1>value1</setting1>
    <setting2>value2</setting2>
    <nested>
        <attr attr-name="optionA">true</attr>
        <attr attr-name="optionB">false</attr>
    </nested>
</configuration>

Example 11: Primitive Value
#

JSON: "Hello, World!"
XML: <message>Hello, World!</message>

Example 12: Array of Objects
#

JSON: [
  {
    "id": 1,
    "name": "Alice",
    "department": "Engineering"
  },
  {
    "id": 2,
    "name": "Bob",
    "department": "Sales"
  },
  {
    "id": 3,
    "name": "Charlie",
    "department": "HR"
  }
]
XML: <company><employeeeeeee><id>1</id><name>Alice</name><department>Engineering</department></employeeeeeee><employeeeeeee><id>2</id><name>Bob</name><department>Sales</department></employeeeeeee><employeeeeeee><id>3</id><name>Charlie</name><department>HR</department></employeeeeeee></company>

Example 13: Custom attr names
#

JSON: [
  {
    "name": "keith",
    "permissions": {
      "read": true,
      "write": false
    }
  }
]
XML: <xml><item><name>keith</name><permissions><value name="read">true</value><value name="write">false</value></permissions></item></xml>

Example 14: Empty array in object
#

JSON: [
  {
    "response": 200,
    "data": []
  }
]
XML: <xml><item><response>200</response><data/></item></xml>

Script
#

myJSON = JSON.parse(arguments[0])
options = JSON.parse(arguments[1])

print (jsonToXml (myJSON, options));

/**
 * json_to_xml.js - Rhino-compatible JSON to XML converter
 * Works with Rhino 1.7R5 and later
 */

/**
 * Converts a JSON object to XML string
 * NOTE: JSON object properties with simple values become child XML elements by default.
 * With wrapSimpleValues=true, they are wrapped in <attrNodeName attrKeyName="key">value</attrNodeName> elements.
 * Nested objects are converted to elements with <attrNodeName attrKeyName="key">value</attrNodeName> children.
 * @param {Object|Array|string|number|boolean} json - The JSON data to convert
 * @param {Object} [options] - Conversion options
 * @param {string} [options.rootName='root'] - Name for the root XML element
 * @param {string} [options.itemName='item'] - Name for array item elements
 * @param {string} [options.attrPrefix='@'] - Prefix for attributes in JSON (e.g., {"@id": 1})
 * @param {string} [optionts.attrNodeName='attr'] - Element name for nested object properties
 * @param {string} [options.attrKeyName='attr-name'] - Attribute name for the key in nested object property elements
 * @param {string} [options.wrapSimpleValues=''] - Tag name to wrap simple values (e.g., 'newtag' for <newtag>value</newtag>). If empty, simple values are not wrapped.
 * @param {boolean} [options.prettyPrint=false] - Format XML with indentation
 * @param {string} [options.indentChar='  '] - Indentation string
 * @param {string} [options.additionalXml=''] - Additional XML string to append after the root element
 * @param {boolean} [options.attrForAllKeys=false] - Apply attrNodeName and attrKeyName to all top-level JSON keys
 * @returns {string} XML string
 */
function jsonToXml(json, options) {
    options = options || {};
    var rootName = options.rootName || 'root';
    var itemName = options.itemName || 'item';
    var attrPrefix = options.attrPrefix || '@';
    var attrNodeName = options.attrNodeName || 'attr';
    var attrKeyName = options.attrKeyName || 'attr-name';
    var wrapSimpleValues = options.wrapSimpleValues || '';
    // Backward compatibility: if wrapSimpleValues is true, default to 'value' as tag name
    if (wrapSimpleValues === true) {
        wrapSimpleValues = 'value';
    }
    var prettyPrint = options.prettyPrint || false;
    var indentChar = options.indentChar || '  ';
    var additionalXml = options.additionalXml || '';
    var attrForAllKeys = options.attrForAllKeys || false;

    function escapeXml(str) {
        if (str == null) return '';
        str = String(str);
        str = str.replace(/&/g, '&amp;');
        str = str.replace(/</g, '&lt;');
        str = str.replace(/>/g, '&gt;');
        str = str.replace(/"/g, '&quot;');
        str = str.replace(/'/g, '&apos;');
        return str;
    }

    function sanitizeElementName(name) {
        if (name == null || name === '') {
            return 'null';
        }
        name = String(name);
        // Replace invalid XML element name characters
        name = name.replace(/[^a-zA-Z0-9_:.-]/g, '_');
        // Ensure it doesn't start with a number or special char
        if (!/^[a-zA-Z_]/.test(name)) {
            name = '_' + name;
        }
        return name;
    }

    function isArray(arg) {
        return Object.prototype.toString.call(arg) === '[object Array]';
    }

    function getKeys(obj) {
        var keys = [];
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                keys.push(key);
            }
        }
        return keys;
    }

    function hasKeys(obj) {
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                return true;
            }
        }
        return false;
    }

    function buildXml(obj, name, indent) {
        var xml = '';
        var attributes = {};
        var children = {};

        // Sanitize element name
        name = sanitizeElementName(name);

        // Handle null/undefined
        if (obj == null) {
            return indent + '<' + name + '/>';
        }

        // Separate attributes from children
        if (typeof obj === 'object' && !isArray(obj)) {
            var keys = getKeys(obj);
            for (var i = 0; i < keys.length; i++) {
                var key = keys[i];
                if (key.indexOf(attrPrefix) === 0) {
                    var attrName = key.substring(attrPrefix.length);
                    attrName = sanitizeElementName(attrName);
                    attributes[attrName] = obj[key];
                } else {
                    var sanitizedKey = sanitizeElementName(key);
                    children[sanitizedKey] = obj[key];
                }
            }
        }

        // Build attribute string
        var attrString = '';
        var attrKeys = getKeys(attributes);
        for (var j = 0; j < attrKeys.length; j++) {
            var attr = attrKeys[j];
            attr = sanitizeElementName(attr);
            attrString += ' ' + attr + '="' + escapeXml(attributes[attr]) + '"';
        }

        // Start tag
        xml += indent + '<' + name + attrString;

        // Handle different types
        if (typeof obj === 'object') {
            if (isArray(obj)) {
                if (obj.length === 0) {
                    xml += '/>';
                } else {
                    xml += '>';
                    if (prettyPrint) xml += '\n';
                    for (var k = 0; k < obj.length; k++) {
                        xml += buildXml(obj[k], itemName, indent + (prettyPrint ? indentChar : ''));
                        if (prettyPrint) xml += '\n';
                    }
                    xml += indent + '</' + name + '>';
                }
            } else if (hasKeys(children)) {
                xml += '>';
                if (prettyPrint) xml += '\n';
                var childKeys = getKeys(children);
                for (var m = 0; m < childKeys.length; m++) {
                    var childName = childKeys[m];
                    var childValue = children[childName];
                    // Sanitize child element name
                    childName = sanitizeElementName(childName);
                    // If attrForAllKeys is set, treat all children as attr nodes
                    if (attrForAllKeys) {
                        xml += indent + (prettyPrint ? indentChar : '');
                        xml += '<' + attrNodeName + ' ' + attrKeyName + '="' + escapeXml(childName) + '">';
                        if (typeof childValue === 'object' && childValue !== null && !isArray(childValue)) {
                            var nestedKeys = getKeys(childValue);
                            for (var n = 0; n < nestedKeys.length; n++) {
                                var nestedKey = nestedKeys[n];
                                var nestedValue = childValue[nestedKey];
                                if (nestedKey.indexOf(attrPrefix) === 0) continue;
                                nestedKey = sanitizeElementName(nestedKey);
                                xml += indent + (prettyPrint ? indentChar + indentChar : '');
                                xml += '<' + attrNodeName + ' ' + attrKeyName + '="' + escapeXml(nestedKey) + '">';
                                if (wrapSimpleValues) {
                                    xml += '<' + wrapSimpleValues + '>' + escapeXml(nestedValue) + '</' + wrapSimpleValues + '>';
                                } else {
                                    xml += escapeXml(nestedValue);
                                }
                                xml += '</' + attrNodeName + '>';
                                if (prettyPrint) xml += '\n';
                            }
                        } else if (isArray(childValue)) {
                            for (var k = 0; k < childValue.length; k++) {
                                xml += buildXml(childValue[k], itemName, indent + (prettyPrint ? indentChar + indentChar : ''));
                                if (prettyPrint) xml += '\n';
                            }
                        } else {
                            if (wrapSimpleValues) {
                                xml += '<' + wrapSimpleValues + '>' + escapeXml(childValue) + '</' + wrapSimpleValues + '>';
                            } else {
                                xml += escapeXml(childValue);
                            }
                        }
                        xml += '</' + attrNodeName + '>';
                        if (prettyPrint) xml += '\n';
                    // If wrapSimpleValues is set, treat all values (including nested objects) as attr nodes
                    } else if (wrapSimpleValues) {
                        if (typeof childValue === 'object' && childValue !== null && !isArray(childValue)) {
                            // For nested objects, create a container element with attr nodes for each property
                            xml += indent + (prettyPrint ? indentChar : '') + '<' + childName + '>';
                            if (prettyPrint) xml += '\n';
                            var nestedKeys = getKeys(childValue);
                            for (var n = 0; n < nestedKeys.length; n++) {
                                var nestedKey = nestedKeys[n];
                                var nestedValue = childValue[nestedKey];
                                nestedKey = sanitizeElementName(nestedKey);
                                xml += indent + (prettyPrint ? indentChar + indentChar : '');
                                xml += '<' + attrNodeName + ' ' + attrKeyName + '="' + escapeXml(nestedKey) + '">';
                                xml += '<' + wrapSimpleValues + '>' + escapeXml(nestedValue) + '</' + wrapSimpleValues + '>';
                                xml += '</' + attrNodeName + '>';
                                if (prettyPrint) xml += '\n';
                            }
                            xml += indent + (prettyPrint ? indentChar : '') + '</' + childName + '>';
                            if (prettyPrint) xml += '\n';
                        } else {
                            // For simple values, just wrap with the wrapSimpleValues tag
                            xml += indent + (prettyPrint ? indentChar : '') + '<' + childName + '>';
                            xml += '<' + wrapSimpleValues + '>' + escapeXml(childValue) + '</' + wrapSimpleValues + '>';
                            xml += '</' + childName + '>';
                            if (prettyPrint) xml += '\n';
                        }
                    } else if (typeof childValue === 'object' && childValue !== null && !isArray(childValue)) {
                        // If child value is an object (not array), convert its properties to <attr> elements
                        xml += indent + (prettyPrint ? indentChar : '') + '<' + childName + '>';
                        if (prettyPrint) xml += '\n';
                        var nestedKeys = getKeys(childValue);
                        for (var n = 0; n < nestedKeys.length; n++) {
                            var nestedKey = nestedKeys[n];
                            var nestedValue = childValue[nestedKey];
                            // Skip @ prefixed keys as they are already handled as attributes
                            if (nestedKey.indexOf(attrPrefix) === 0) continue;
                            nestedKey = sanitizeElementName(nestedKey);
                            xml += indent + (prettyPrint ? indentChar + indentChar : '');
                            xml += '<' + attrNodeName + ' ' + attrKeyName + '="' + escapeXml(nestedKey) + '">';
                            xml += escapeXml(nestedValue);
                            xml += '</' + attrNodeName + '>';
                            if (prettyPrint) xml += '\n';
                        }
                        xml += indent + (prettyPrint ? indentChar : '') + '</' + childName + '>';
                        if (prettyPrint) xml += '\n';
                    } else {
                        xml += buildXml(childValue, childName, indent + (prettyPrint ? indentChar : ''));
                        if (prettyPrint) xml += '\n';
                    }
                }
                xml += indent + '</' + name + '>';
            } else {
                xml += '/>';
            }
        } else {
            xml += '>' + escapeXml(obj) + '</' + name + '>';
        }

        return xml;
    }

    // Handle array at root level
    if (isArray(json)) {
        var arrXml = '';
        if (prettyPrint) arrXml += '\n';
        for (var i = 0; i < json.length; i++) {
            arrXml += buildXml(json[i], itemName, (prettyPrint ? indentChar : ''));
            if (prettyPrint) arrXml += '\n';
        }
        return '<' + rootName + '>' + arrXml + '</' + rootName + '>' + additionalXml;
    }

    // Handle primitive at root level
    if (typeof json !== 'object') {
        return '<' + rootName + '>' + escapeXml(json) + '</' + rootName + '>' + additionalXml;
    }

    // Handle object at root level
    return buildXml(json, rootName, '') + additionalXml;
}