Query XML Using JSONata

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