Convert XML to JSON

Turn an XML file or API response into JSON, either as a straight conversion or reshaped into exactly the JSON structure you need.

Sample input

<?xml version="1.0" encoding="UTF-8"?>
<orders>
  <order id="1001" status="paid">
    <customer>Alice Smith</customer>
    <total currency="EUR">120.50</total>
  </order>
  <order id="1002" status="open">
    <customer>Bob "Bobby" Brown</customer>
    <total currency="EUR">80.00</total>
  </order>
</orders>

One-line conversion

bafi -i orders.xml -t '?{{toJSON .}}'
{
  "orders": {
    "order": [
      {
        "-id": "1001",
        "-status": "paid",
        "customer": "Alice Smith",
        "total": {
          "#text": "120.50",
          "-currency": "EUR"
        }
      },
      {
        "-id": "1002",
        "-status": "open",
        "customer": "Bob \"Bobby\" Brown",
        "total": {
          "#text": "80.00",
          "-currency": "EUR"
        }
      }
    ]
  }
}

How XML maps to the data the template sees:

  • Attributes become keys prefixed with -: id="1001" is -id.
  • Text of an element that also has attributes is stored under #text: <total currency="EUR">120.50</total> becomes {"-currency": "EUR", "#text": "120.50"}.
  • Repeated elements become a list; a single element is not a list (see below).
  • All values are text; XML has no numbers.

Reshape into your own JSON

Pick the fields you need and give them your own names. toJSON on a single value produces a correctly quoted and escaped JSON string, so names containing quotes stay valid JSON.

[
{{- range $i, $o := .orders.order}}{{if $i}},{{end}}
  {"id": {{index $o "-id"}}, "customer": {{toJSON $o.customer}}, "total": {{index $o.total "#text"}}, "paid": {{eq (index $o "-status") "paid"}}}
{{- end}}
]
bafi -i orders.xml -t orders-json.tmpl
[
  {"id": 1001, "customer": "Alice Smith", "total": 120.50, "paid": true},
  {"id": 1002, "customer": "Bob \"Bobby\" Brown", "total": 80.00, "paid": false}
]

One element or many

If the XML contains a single <order>, .orders.order is that order itself, not a list, and range iterates over its fields instead. Wrap it with mustArray, which turns a single value into a one-item list and leaves lists alone:

<orders>
  <order id="1001"><customer>Alice Smith</customer></order>
</orders>
bafi -i single.xml -t '?{{range mustArray .orders.order}}{{.customer}}{{end}}'
Alice Smith

From an API

curl -fs https://example.com/api/orders.xml | bafi -f xml -t orders-json.tmpl -o orders.json

-f xml is needed because stdin has no file extension. curl -f makes curl fail on HTTP errors instead of piping an error page into bafi.

Related: JSON to XML · XML to CSV · SOAP response to JSON