I have issues with accessing fields of unmarshalled XML files, which contain optional tags. Here is a simple example I made up for a more complex case:
<people>
<persons>
<person>
<id>222</id>
<pets>
<pet>
<name age="2">Harry</name>
</pet>
<pet>
<name>Tiffany</name>
</pet>
</pets>
</person>
<person>
<id>111</id>
<pets>
<pet value="1"></pet>
</pets>
<spouse>Frank</spouse>
</person>
</persons>
</people>
Notice that the second person has a spouse and the first does not. Additionally, the pets of the first person have names and the pets of the second person do not. The pet named Harry has also an age attribute. What I'm trying to show is that my XML files can have varying data, because of optional fields.
Here are my model classes for JAXB:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class People {
#XmlElementWrapper
#XmlElement(name="person")
private List<Person> persons;
public List<Person> getPersons() {
return persons;
}
public void setPersons(List<Person> persons) {
this.persons= persons;
}
}
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Person {
#XmlElement
private int id;
#XmlElementWrapper
#XmlElement(name="pet")
private List<Pet> pets;
#XmlElement
private String spouse;
//getters and setters
}
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class pet {
#XmlAttribute
private int age;
#XmlValue
private String name;
//getters and setters
}
Now, let's say that I just want to print all names of pets.
for (Person person : people.getPersons()) {
for (Pet pet : person.getPets()) {
System.out.println("Pet name: " + pet.getName());
}
}
I'm getting a NullPointerException, if an inner tag is missing. Interestingly enough though, if you just search for a field in the people layer, it will just skip the XML tag as if it does not exist, like:
for (Person person : people.getPersons()) {
System.out.println("Spouse: " + person.getSpouse());
}
The above command works even though the first person does not have a spouse. It just provides the string "null", which works well for me.
A solution that I've tried - wrap each field in an if or try-catch statement (I prefer not to do this as there are hundreds of XML tags). Let me know if you have any suggestions. Thanks.
The NullPointerException in your example is quite obvious.
System.out.println("Pet name: " + people.getPerson().get(i).getPet().getName());
The problem is, when you have no pets,getPet() returns null. Subsequently, you call getName() on null, hence you get the exception. In your second example, the execption does not occur, because getSpouse() returns null, but System.out.println(...) converts it into the string null automatically.
Even though it may seem tedious, you will have to check your attributes explicitly for null, as you traverse your lists or fields. Never do that with try-catch! Exception handling is a heavy mechanism and you should never abuse this concept for null checks.
Related
I want to unmarshall a XML-File with mixed Content. I found a thread on stackoverflow which seemed appropriate (JAXB- #XmlMixed usage for reading #XmlValue and #XmlElement) where the user bdoughan defined 3 Use-Cases to deal with mixed content.
The third use case would keep the text between the tags in a single String variable and save the elements in a List. Which is what I wanted. Unfortunately I couldn't get it to work and the thread is quite old and maybe outdated.
I've tried the Usecase #3 with a List of Objects and a List of my Reference Class. Also I tried #XmlElement and #XmlValue Annotations.
I'm using the javax.xml.bind jaxb-api in version 2.3.1 and the org.glassfish.jaxb jaxb-runtime in version 2.3.1 in a Maven Projec with Java SE Version 12.0.2.
A Sample XML I tested with
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Date>
2018.06.27
<reference id="AnyId1">
</reference>
</Date>
My Class Representation
#XmlRootElement(name="Date")
public class TestPojo {
#XmlMixed
public String getTextContent() {
return textContent;
}
public void setTextContent(String textContent) {
this.textContent = textContent;
}
#XmlElementRef(name="reference", type = Reference.class)
public List<Object> getRef() {
return ref;
}
public void setRef(List<Object> ref) {
this.ref = ref;
}
String textContent;
List<Object> ref = new ArrayList<Object>();
}
I'd expect that the xml is unmarshalled into a POJO object and the right values are assigned. The Objects variables (textContent & ref) are null after unmarshalling.
You could try this:
Using a Reference class as below,
#XmlAccessorType(XmlAccessType.FIELD)
public class Reference {
#XmlAttribute
private String id;
}
And your Root class,
#XmlRootElement(name="Date")
public class TestPojo {
#XmlMixed
#XmlAnyElement
private List<Object> textContent;
#XmlElement
private Reference reference;
}
This will unmarshall giving you the reference element and everything else in a List.
For the example you have it will be 2 entries. The date value/text along with tab character (\t) and new line characters (\n), and another entry with new line character.
So you can use this list to process the contents and use what you want.
If there is a cleaner solution, I am interested. Cheers
Update to answer to comment:
In order to be more clear with the fix. What I did was using #XmlElement instead of #XmlElementRef for a single Reference instead of list (cos that's what I saw in the xml).
Also I added the #XmlAnyElement annotation for the mixed content made it a list. This is what fixed it. So sticking with your class, it would look like below:
#XmlRootElement(name="Date")
public class TestPojo {
List<Object> textContent;
Reference ref;
#XmlMixed
#XmlAnyElement
public List<Object> getTextContent() {
return textContent;
}
public void setTextContent(List<Object> textContent) {
this.textContent = textContent;
}
#XmlElement(name="reference")
public Reference getRef() {
return ref;
}
public void setRef(Reference ref) {
this.ref = ref;
}
}
The #XmlAccessorType saved me time from writing getters and setters. For an explanation of what this annotation does with an example (and in relation to #XmlElement, check this:
What is the difference between using #XmlElement before field and before getter declaration?
I have a mapping from a DTO model to a JAXB generated datamodel that is full of JAXBElement<> wrapper objects.
For example, there is a class Person defined as (getters and setters are omitted):
public class Person {
private JAXBElement<Name> name;
}
Name is defined as:
public class Name {
private String value;
}
For constructing JAXBElement I created an ObjectFactory:
public class NameFactory extends ObjectFactory<JAXBElement<Name>> {
protected JAXBElement<Name> createObject(#Nonnull Class<?> context) {
// here, ObjectFactory is the JAXB generated ObjectFactory
return new ObjectFactory().createName();
}
}
In my ConfigurableMapper I create a class mapping from PersonDto to Person like so:
factory.createClassMap(PersonDto.class, Person.class)
.field("name", "name.value.value")
.register;
With this config, the mapping of a PersonDto with no name (name equals null) will result in a Person element that has a name member with its value set to null. This is probably better explained by showing the XML that is generated after performing the class mapping:
<Person>
<Name>
<value></value>
</Name>
</Person>
In my case, this XML is invalid, when there is a Name element, its value should always be non-null. The XML should therefore be:
<Person>
</Person>
Is it possible to prevent Orika from constructing the Name object, knowing its value will be set to null?
Going through the code again a day later with a clear mind, it turns out that Orika doesn't create the wrapper element (as I expected it wouldn't) and that it was a different issue entirely...
I have to create POJOs so that I can generate XML using JAXB for the below XML (Just a sample because child elements may go beyond 40).
Here, important thing to note is that I cannot declare these elements as properties in POJO because I won't be knowing the elements name.
<User>
<FirstName>Mahendra</FirstName>
<MiddleName>Singh</MiddleName>
<LastName>Dhoni</LastName>
<Organization>
<Name>Electronics</Name>
<id>elc001</id>
</Organization>
<Manager>
<Name>Sourabh</Name>
<id>emp_001</id>
</Manager>
</User>
I have created POJO for above XML as:
Fields1.java : For elements having value only.
public class Fields1
{
#XmlTransient
public String fieldName1;
#XmlValue
public String value;
// getter,setter
}
Fields2.java : For elements having child elements.
public class Fields2
{
#XmlTransient
public String fieldName2;
#XmlElement(name="NAME")
public String name;
#XmlElement(name="ID")
public String id;
// getter,setter
}
User.java : Root element class
public class User
{
#XmlVariableNode("fieldName1")
public List<Fields1> fields1;
#XmlVariableNode("fieldName2")
public List<Fields2> fields2;
// getter, setter
}
Here, #XmlVariableNode is helping me to generate elements name dynamically.
1. But, it only works fine if there is only single property
2. and if, there are two properties then it just works for the first one and ignores the next.
AFAIK, multiple #XmlVariableNodes in the same class are not possible. EclipseLink's documentation states:
Since this [#XmlVariableNode] makes use of the any logic during unmarshal and MOXy only
handles one Any mapping on a class if a class makes use of the
XmlVariableNode annotation then that class can not have XmlAnyElement
annotations or any other variables that would cause AnyObject or
AnyCollection mappings to be created.
(Source: EclipseLink/DesignDocs/406697)
You might be able to solve your problem by using nested #XmlVariableNodes:
public class TopLevelField {
#XmlTransient
public String fieldName;
#XmlVariableNode("fieldName")
public List<NestedField> fields;
// ...
}
public class NestedField {
#XmlTransient
public String fieldName;
#XmlValue
public String value;
// ...
}
#XmlRootElement
public class User {
#XmlVariableNode("fieldName")
public List<TopLevelField> fields;
}
I am using #XmlRootElement annotation to get XML data from the database.
Right now, if I put #XmlTransient to getters, the fields are ignored.
For example:
public class Student {
private Integer studentId;
private String studentName;
#XmlTransient // Do not get student id
public Integer getStudentId() {
return this.studentId;
}
public String getStudentName() {
return this.studentName;
}
...// Setter goes here
Then, student ids are not appear in the XML file.
However, can I do this in the opposite way? I want to specify fields that I want to have in the XML file - there are too many fields in the Student class.
My server(Spring Framework 3.2.3) also uses the Jackson library, so I wonder I could use it if that is possible.
You could annotate your class with:
#XmlAccessorType(XmlAccessType.NONE)
Now you have to explicitly map properties in order to be serialized. See the Javadoc: http://docs.oracle.com/javaee/7/api/javax/xml/bind/annotation/XmlAccessType.html
I got a requirement that I have to map my xml to java object without parsing it, but the problem is like that in xml tag names would be same, for example,
<response>
<employee>
<name>Sharique</name>
<name>24</name>
<name>India</name>
</employee>
</response>
and class would be like this
public class Employee{
private String empName;
private int age;
private String country;
//getters and setters
}
Please help!!
If it can be done using spring than that would be very nice
If you leverage EclipseLink MOXy as your JAXB (JSR-222) provider then you can use our #XmlPath extension for this use case.
#XmlAccessorType(XmlAccessType.FIELD)
public class Employee{
#XmlPath("name[1]/text()")
private String
#XmlPath("name[2]/text()")
private int age;
#XmlPath("name[3]/text()")
private String country;
//getters and setters
}
For More Information
I have written more about the #XmlPath extension on my blog:
http://blog.bdoughan.com/2010/07/xpath-based-mapping.html
Not required, As per javax.xml.bind.annotation you can do like below,
#XmlElement(name="name")
private String empName;
So now the empName in your java class will be mapped to name attribute in your XML.
and your XML should not have 'name' as name for all attributes. it cannot differentiate, so you need to use different tags in your XML for other elements like age and so on ans map accordingly as i stated above in your POJO.
You really got some weird XML there. I thing data binding will not help in this case, but you can do it with data projection. (Disclosure: I'm affilited with that project)
public class ParseResponse {
public interface Employee {
#XBRead("./name[1]")
String getName();
#XBRead("./name[2]")
int getAge();
#XBRead("./name[3]")
String getCountry();
}
public static void main(String[] args) {
List<Employee> employees = new XBProjector().io().url("res://response.xml").evalXPath("//employee").asListOf(Employee.class);
for (Employee employee:employees) {
System.out.println(employee.getName());
System.out.println(employee.getAge());
System.out.println(employee.getCountry());
}
}
}
this program prints out
Sharique
24
India
if you fix the XML closing tags.