Query XML Using JSONata

Updated: May 13, 2026

This example shows how to read an XML document (or any other supported format) and query or transform it using JSONata expressions with DataPipeline.

 

Input XML File

<?xml version='1.0' encoding='UTF-8'?>
<document>
  <record>
    <id>0001</id>
    <type>donut</type>
    <name>Cake</name>
    <ppu>0.55</ppu>
    <batters>
      <batter>
        <id>1001</id>
        <type>Regular</type>
      </batter>
      <batter>
        <id>1002</id>
        <type>Chocolate</type>
      </batter>
      <batter>
        <id>1003</id>
        <type>Blueberry</type>
      </batter>
      <batter>
        <id>1004</id>
        <type>Devil's Food</type>
      </batter>
    </batters>
    <topping>
      <id>5001</id>
      <type>None</type>
    </topping>
    <topping>
      <id>5002</id>
      <type>Glazed</type>
    </topping>
    <topping>
      <id>5005</id>
      <type>Sugar</type>
    </topping>
    <topping>
      <id>5007</id>
      <type>Powdered Sugar</type>
    </topping>
    <topping>
      <id>5006</id>
      <type>Chocolate with Sprinkles</type>
    </topping>
    <topping>
      <id>5003</id>
      <type>Chocolate</type>
    </topping>
    <topping>
      <id>5004</id>
      <type>Maple</type>
    </topping>
  </record>
  <record>
    <id>0002</id>
    <type>donut</type>
    <name>Raised</name>
    <ppu>0.55</ppu>
    <batters>
      <batter>
        <id>1001</id>
        <type>Regular</type>
      </batter>
    </batters>
    <topping>
      <id>5001</id>
      <type>None</type>
    </topping>
    <topping>
      <id>5002</id>
      <type>Glazed</type>
    </topping>
    <topping>
      <id>5005</id>
      <type>Sugar</type>
    </topping>
    <topping>
      <id>5003</id>
      <type>Chocolate</type>
    </topping>
    <topping>
      <id>5004</id>
      <type>Maple</type>
    </topping>
  </record>
</document>

 

Java code listing

package com.northconcepts.datapipeline.examples.jsonata;

import com.northconcepts.datapipeline.core.Record;
import com.northconcepts.datapipeline.core.ValueNode;

import java.io.FileReader;

import static com.northconcepts.datapipeline.jsonata.Jsonata.jsonata;

public class QueryXmlUsingJSONata {

    public static void main(String[] args) throws Throwable {
        try (FileReader fileReader = new FileReader("data/input/nested-data.xml")) {
            ValueNode xmlRoot = Record.fromXml(fileReader);

            printSection("Basic Selection");
            runQuery("Query 1 - Donut Names", "document.record.name", xmlRoot);

            runQuery("Query 2 - Batter Types", "document.record.batters.batter.type", xmlRoot);

            printSection("Filtering");
            runQuery("Query 3 - Donut Named Raised", "document.record[name='Raised']", xmlRoot);
            runQuery("Query 4 - Donut with Chocolate with Sprinkles Topping", "document.record[topping[type='Chocolate with Sprinkles']].name", xmlRoot);

            printSection("Reshaping");
            runQuery("Query 5 - Name With Batter And Topping Counts",

                    "document.record.{'name': name, 'batterCount': $count(batters.batter), 'toppingCount': $count(topping)}",
                    xmlRoot);
            runQuery("Query 6 - Donut Summary",
                    "document.record.{'id': id, 'name': name, 'pricePerUnit': ppu}",
                    xmlRoot);

            printSection("Metrics");
            runQuery("Query 7 - Total Donuts", "$count(document.record)", xmlRoot);
            runQuery("Query 8 - Average Price Per Unit", "$average(document.record.ppu.$number())", xmlRoot);
        }
    }

    private static void runQuery(String title, String expression, ValueNode data) {
        System.out.println(title);
        System.out.println("Expression: " + expression);
        System.out.println("Result: ");
        System.out.println(jsonata(expression).evaluate(data));
    }

    private static void printSection(String title) {
        System.out.println("==============================================================");
        System.out.println(title);
    }
}

 

Code walkthrough

  1. Read XML into memory
    • Record.fromXml(fileReader) parses the file and returns a ValueNode tree. All queries run against that tree.
  2. Execute JSONata expressions
    • runQuery prints the query name, the expression, and the evaluated result from jsonata(expression).evaluate(data)
  3. Query patterns used
    • Basic Selection: These expressions navigate fields and nested arrays.
      • document.record.name
      • document.record.batters.batter.type
    • Filtering: These expressions keep only records that match conditions.
      • document.record[name='Raised']
      • document.record[topping[type='Chocolate with Sprinkles']].name
    • Reshaping: These expressions build new objects with only the fields you want.
      • document.record.{'name': name, 'batterCount': $count(batters.batter), 'toppingCount': $count(topping)}
      • document.record.{'id': id, 'name': name, 'pricePerUnit': ppu}
    • Metrics: These expressions compute aggregate values.
      • $count(document.record)
      • $average(document.record.ppu.$number())

 

Console Output

==============================================================
Basic Selection
Query 1 - Donut Names
Expression: document.record.name
Result: 
[Cake, Raised]
Query 2 - Batter Types
Expression: document.record.batters.batter.type
Result: 
[Regular, Chocolate, Blueberry, Devil's Food, Regular]
==============================================================
Filtering
Query 3 - Donut Named Raised
Expression: document.record[name='Raised']
Result: 

                Record (MODIFIED) (is child record) (has child records) {
                    0:[id]:STRING=[0002]:String
                    1:[type]:STRING=[donut]:String
                    2:[name]:STRING=[Raised]:String
                    3:[ppu]:STRING=[0.55]:String
                    4:[batters]:RECORD=[
                        Record (MODIFIED) (is child record) (has child records) {
                            0:[batter]:RECORD=[
                                Record (MODIFIED) (is child record) {
                                    0:[id]:STRING=[1001]:String
                                    1:[type]:STRING=[Regular]:String
                                }]:Record
                        }]:Record
                    5:[topping]:ARRAY of RECORD=[[
                        Record (MODIFIED) (is child record) {
                            0:[id]:STRING=[5001]:String
                            1:[type]:STRING=[None]:String
                        }, 
                        Record (MODIFIED) (is child record) {
                            0:[id]:STRING=[5002]:String
                            1:[type]:STRING=[Glazed]:String
                        }, 
                        Record (MODIFIED) (is child record) {
                            0:[id]:STRING=[5005]:String
                            1:[type]:STRING=[Sugar]:String
                        }, 
                        Record (MODIFIED) (is child record) {
                            0:[id]:STRING=[5003]:String
                            1:[type]:STRING=[Chocolate]:String
                        }, 
                        Record (MODIFIED) (is child record) {
                            0:[id]:STRING=[5004]:String
                            1:[type]:STRING=[Maple]:String
                        }]]:ArrayValue
                }
Query 4 - Donut with Chocolate with Sprinkles Topping
Expression: document.record[topping[type='Chocolate with Sprinkles']].name
Result: 
Cake
==============================================================
Reshaping
Query 5 - Name With Batter And Topping Counts
Expression: document.record.{'name': name, 'batterCount': $count(batters.batter), 'toppingCount': $count(topping)}
Result: 
[Record (MODIFIED) (is child record) {
    0:[name]:STRING=[Cake]:String
    1:[batterCount]:INT=[4]:Integer
    2:[toppingCount]:INT=[7]:Integer
}
, Record (MODIFIED) (is child record) {
    0:[name]:STRING=[Raised]:String
    1:[batterCount]:INT=[1]:Integer
    2:[toppingCount]:INT=[5]:Integer
}
]
Query 6 - Donut Summary
Expression: document.record.{'id': id, 'name': name, 'pricePerUnit': ppu}
Result: 
[Record (MODIFIED) (is child record) {
    0:[id]:STRING=[0001]:String
    1:[name]:STRING=[Cake]:String
    2:[pricePerUnit]:STRING=[0.55]:String
}
, Record (MODIFIED) (is child record) {
    0:[id]:STRING=[0002]:String
    1:[name]:STRING=[Raised]:String
    2:[pricePerUnit]:STRING=[0.55]:String
}
]
==============================================================
Metrics
Query 7 - Total Donuts
Expression: $count(document.record)
Result: 
2
Query 8 - Average Price Per Unit
Expression: $average(document.record.ppu.$number())
Result: 
0.55
Mobile Analytics