Java 8 or later Library for mapping XPaths to JSON-attributes. It parses org.w3c.dom XML Nodes and creates Jackson JsonNodes.
The most recent release is unXml 0.9, released November 16, 2017.
To add a dependency on unXml using Maven, use the following:
<dependency>
<groupId>com.nerdforge</groupId>
<artifactId>unxml</artifactId>
<version>0.9</version>
</dependency>
A Parser | Created by | has apply(node) that does the transformation |
---|---|---|
Parser<ObjectNode> | parsing.obj().build() |
Node ➝ ObjectNode |
Parser<ArrayNode> | parsing.arr(...).build() |
Node ➝ ArrayNode |
Document can also be used as input, since it extends Node.
To create a Parser you first need an instance of Parsing.
Parsing parsing = ParsingFactory.getInstance().create();
// create parser that will output a Jackson ObjectNode
Parser<ObjectNode> parser = parsing.obj()
.attribute("resultKey", "//my-xpath")
.build();
Parser<ObjectNode> parser2 = parsing.obj("//my-root")
.attribute("id", "@id")
.build();
// create parser that will output a Jackson ArrayNode
Parser<ArrayNode> parser3 = parsing.arr("//my-root)
.attribute("id", "@id")
.build();
By using the as
-method on ObjectNodeParserBuilder or ArrayNodeParserBuilder, you can have Jackson instansiate an Object
or a List
as part of the parsing process.
// create a parser that will output an Object instance
ObjectParser<Foo> fooParser = parsing.obj(...).attribute(...).as(Foo.class);
Foo foo = fooParser.apply(xmlDocument);
// create a parser that will output a List of objects
ObjectParser<List<Bar>> barsParser = parsing.arr(...).as(Bar.class);
List<Bar> bars = barsParser.apply(xmlDocument);
<root>
<id>1</id>
<title>mytitle</title>
</root>
import com.nerdforge.unxml.Parsing;
import com.nerdforge.unxml.factory.ParsingFactory;
...
public class MyController {
public ObjectNode getJsonFromXml(String inputXmlString) {
Parsing parsing = ParsingFactory.getInstance().create(); // (1)
Document document = parsing.xml().document(inputXmlString); // (2)
Parser<ObjectNode> parser = parsing.obj("root") // (3)
.attribute("id", "id", parsing.number()) // (4)
.attribute("title") // (5)
.build(); // (6)
ObjectNode node = parser.apply(document); // (7)
return node;
}
}
- Uses the ParsingFactory to get an instance of Parsing.
- Parsing also gives access to an XML-utility object by using the
xml()
-method. - The
Parsers.obj()
returns an ObjectNodeParserBuilder - The resulting json object gets attribute with key =
id
.
- The value is first read as the
String
content of the xpath:/root/id
in the xml. - It will apply the
SimpleParser.numberParser()
method, to theString
content, and return the attribute as a JSNumber
.
- The resulting json object gets an attribute with id =
title
, and from the content on the xpathtitle
. - Creates a Parser that will output an ObjectNode.
- Node ➝ ObjectNode
{
"id":1,
"title":"mytitle"
}
<root>
<entry>
</entry>
<entry>
<list>
<value>x</value>
<value>y</value>
</list>
</entry>
</root>
public class MyController {
@Inject private Parsing parsing; // (1)
public ArrayNode getArrayFromXml(String inputXmlString) {
Document document = parsing.xml().document(inputXmlString);
Parser<ArrayNode> parser = parsing.arr("/root/entry",
parsing.arr("list/value", parsing.text())
).build(); // (2)
ArrayNode node = parser.apply(document);
return node;
}
}
- By using Google Guice you can directly inject a Parsing object into your class. (Remember to
install
the UnXmlModule in your module). - Creates a Parser<ArrayNode>, that can map to an ArrayNode of ArrayNode of
Strings
.
- The first
arr()
will pick out eachentry
node (in the xml-file). - The second
arr()
will pick out eachvalue
in thelist
.
[[],["x","y"]]
You can of course combine Parser with Parser and predefined parsers to map to more complex structures.
<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<entry id="1">
<name>Homer Simpson</name>
<birthday>1956-03-01</birthday>
<email xmlns="http://www.w3.org/2007/app">chunkylover53@aol.com</email>
<phoneNumbers>
<home>5551234</home>
<mobile>5555678</mobile>
<work>5559991</work>
</phoneNumbers>
</entry>
</feed>
public class MyController {
public ArrayNode getUsersFromXml(String inputXmlString) {
Parsing parsing = ParsingFactory.getInstance(namespaces()).create(); // (1)
Document input = parsing.xml().document(inputXmlString);
Parser dateParser = parsing.simple().dateParser(DateTimeFormatter.ofPattern("yyyy-MM-dd")); // (2)
Parser<ArrayNode> parser = parsing.arr("/a:feed/a:entry", // (3)
parsing.obj()
.attribute("id", "@id", parsing.number()) // (4)
.attribute("name", "a:name")
.attribute("birthday", "a:birthday", dateParser)
.attribute("email", "app:email") // (5)
.attribute("phoneNumbers", parsing.arr("a:phoneNumbers/*", parsing.with(Integer::parseInt))) // (6)
).build();
ArrayNode node = parser.apply(input);
return node;
}
private Map<String, String> namespaces(){
return new HashMap<String, String>(){{
put("a", "http://www.w3.org/2005/Atom");
put("app", "http://www.w3.org/2007/app");
}};
}
}
- Creates the instance of
Parsing
, but with XML-namespaces. - Creates a
dateParser
that will parse dates with the formatyyyy-MM-dd
. - Uses the preconfigured namespace
a
to do selections. - The xpath selects on an attribute in the xml
- Use the preconfigured namespace
app
to select the email. - We do a xpath selection on a wildcard, to create an array. The
String
contents of the nodes are parsed intoIntegers
.
[{
"birthday":[1956,3,1], // (1)
"name":"Homer Simpson",
"id":1,
"email":"chunkylover53@aol.com",
"phoneNumbers":[5551234,5555678,5559991]
}]
- The Jackson library will map a Java LocalDate as a JavaScript
Array
ofNumbers
. See the documentation.
Since a Parser is a functional interface, it can be mapped directly, like this:
Parsing parsing = ParsingFactory.getInstance(namespaces).create();
Parser<ObjectNode> parser = parsing.obj()... // se above for examples
Document document = parsing.xml().document(inputXmlString);
// Apply to an Optional
Optional<ObjectNode> result = Optional.of(document).map(parser); // (1)
// Apply to a Stream
List<Document> documents = ...
List<ObjectNode> results = documents.stream().map(parser).collect(toList()); // (2)
- Applies the parser directly without doing a
Optional.of(document).map(parser::apply)
- Shorthand for
documents.stream().map(parser::apply).collect(toList())