Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
EclipseLink/UserGuide/MOXy/Advanced Concepts/XPath Predicates
EclipseLink MOXy
EclipseLink | |
Website | |
Download | |
Community | |
Mailing List • Forums • IRC • mattermost | |
Issues | |
Open • Help Wanted • Bug Day | |
Contribute | |
Browse Source |
Key API
Contents
Mapping using XPath Predicates
As we have seen in previous examples, by default, JAXB will use the Java field name as the XML element name:
public class Customer { @XmlElement private String firstName; @XmlElement private String lastName; } |
<?xml version="1.0" encoding="UTF-8"?> <customer> <firstName>Bob</firstName> <lastName>Roberts</lastName> </customer> |
Or, the XML name can be customized using the name attribute of the @XmlElement annotation:
public class Customer { @XmlElement(name="f-name") private String firstName; @XmlElement(name="l-name") private String lastName; } |
<?xml version="1.0" encoding="UTF-8"?> <customer> <f-name>Bob</f-name> <l-name>Roberts</l-name> </customer> |
However, sometimes elements need to be mapped based on their position in the document, or based on an attribute value of an element:
<?xml version="1.0" encoding="UTF-8"?> <node> <name>Jane</name> <name>Doe</name> <node name="address"> <node name="street">123 A Street</node> </node> <node name="phone-number" type="work">555-1111</node> <node name="phone-number" type="cell">555-2222</node> </node>
For cases like this, EclipseLink MOXy allows you to use XPath predicates to define an expression that will specify the XML element's name.
XPath Predicates
An XPath predicate represents an expression that will be evaluated against the element specified. For example, the XPath statement:
node[2]
Would match the second occurrence of the node element ("DEF"):
<?xml version="1.0" encoding="UTF-8"?> <data> <node>ABC</node> <node>DEF</node> </data>
Predicates can also match based on an attribute value:
node[@name='foo']
Would match the node element with the attribute name="foo" ("ABC"). It would not match the node that contains "DEF":
<?xml version="1.0" encoding="UTF-8"?> <data> <node name="foo">ABC</node> <node name="bar">DEF</node> </data>
Mapping based on Position
In the following example, our XML contains two name elements; the first occurrence of name should represent the Customer's first name, and the second name will be their last name. To map this, we will specify XPath expressions for each property that will match the appropriate XML element. Note that we also use @XmlType(propOrder) to ensure that our elements will always be in the proper positions.
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlRootElement @XmlType(propOrder={"firstName", "lastName"}) @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("name[1]/text()") private String firstName; @XmlPath("name[2]/text()") private String lastName; ... }
This same configuration can be expressed in an EclipseLink XML Bindings document as follows:
... <java-type name="Customer"> <xml-root-element/> <xml-type prop-order="firstName lastName"/> <java-attributes> <xml-element java-attribute="firstName" xml-path="name[1]/text()"/> <xml-element java-attribute="lastName" xml-path="name[2]/text()"/> </java-attributes> </java-type> ...
This will give us the desired XML representation:
<?xml version="1.0" encoding="UTF-8"?> <customer> <name>Bob</name> <name>Smith</name> </customer>
Mapping based on an Attribute Value
Since EclipseLink MOXy 2.3, you can also map to an XML element based on an Attribute value. In this example, all of our XML elements are named node, differentiated by the value of their name attribute:
<?xml version="1.0" encoding="UTF-8"?> <node> <node name="first-name">Bob</node> <node name="last-name">Smith</node> <node name="address"> <node name="street">123 A Street</node> </node> <node name="phone-number" type="work">555-1111</node> <node name="phone-number" type="cell">555-2222</node> </node>
We can use an XPath in the form of element-name[@attribute-name='value'] to map each Java field:
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlRootElement(name="node") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("node[@name='first-name']/text()") private String firstName; @XmlPath("node[@name='last-name']/text()") private String lastName; @XmlPath("node[@name='address']") private Address address; @XmlPath("node[@name='phone-number']") private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>(); ... }
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlAccessorType(XmlAccessType.FIELD) public class Address { @XmlPath("node[@name='street']/text()") private String street; ... }
package example; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) public class PhoneNumber { @XmlAttribute private String type; @XmlValue private String number; ... }
"Self" Mappings
EclipseLink allows you to configure your one-to-one mappings so the data from the target object will appear inside the source object's XML element. Expanding on the previous example, we could map the Address information so that it would appear directly under the customer element, and not wrapped in its own element. This is referred to as a "self" mapping, and is achieved by setting the target object's XPath to "." (dot).
The following example demonstrates a self mapping declared in annotations.
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlRootElement(name="node") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("node[@name='first-name']/text()") private String firstName; @XmlPath("node[@name='last-name']/text()") private String lastName; @XmlPath(".") private Address address; @XmlPath("node[@name='phone-number']") private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>(); ... }
Using a self mapping, EclipseLink produces the desired XML. The street data is stored in the root node.
<?xml version="1.0" encoding="UTF-8"?> <node> <node name="first-name">Bob</node> <node name="last-name">Smith</node> <node name="street">123 A Street</node> <node name="phone-number" type="work">555-1111</node> <node name="phone-number" type="cell">555-2222</node> </node>