-
Notifications
You must be signed in to change notification settings - Fork 0
Extended parsing tutorial
This article describes the brief usage of the project for XML file reading into objects.
- Prerequisities - explains content of XML file and required classes
-
Basic initialization - explains how default settings and xml-serializer is created
- Custom settings - how custom settings are specified for xml-serializer
-
Managing lists - How lists are managed in deserializatin - important!
- List items as sub-types of supertype - how list can contains element of different type
- Custom value parsing - how custom value parsing into specific type can be used
The XML file will have following content.
<?xml version="1.0" encoding="UTF-8"?>
<database>
<persons>
<person name="John" surname="Doe" birthDate="2018-01-20">
<address city="Ostrava" street="Stodolni" houseNumber="23/1284" />
<phones>
<homePhone prefix="+420" number="597 493 829" />
<mobilePhone prefix="+420" number="594 892 384" />
</phones>
</person>
<person>
<name>Jane</name>
<surname>Doe</surname>
<birthDate>2000-12-24</birthDate>
<address>
<city>Bruntál</city>
<street>Školní</street>
<houseNumber>2</houseNumber>
<!-- GPS is optional -->
<gps latitude="49.9891146" longitude="17.4568346" />
</address>
<phones>
</phones>
</person>
</persons>
</database>
Note that the first person data are stored mostly using xml-attributes but the second person has data stored mostly using _xml-elements with values. The data will be read the same way, so basically:
- primitive and simple types can be stored in xml-attribute or in value of xml-element,
- complex types (classes) can be stored typically only in xml-element with nested xml-attributes or elements,
- both behaviour can be later adjusted by custom value and element parsers.
To fill the data we need classes. (Remark: all the following code listings have package, import and all getters hidden.)
Database class representing the root object is simple. There is not a need to root xml-element have the same name as the class:
public class Database {
private List persons;
}
Class Person represent a person:
public class Person {
private String name;
private String surname;
private java.time.LocalDate birthDate;
private Address address;
private List phones;
}
Address class. Note that gps is marked @XmlOptional:
public class Address {
private String city;
private String street;
private String houseNumber;
@XmlOptional
private Gps gps = null;
}
Phone class is abstract and has two descendants. Note that the fields are declared in the superclass but will be read too.
public abstract class Phone {
private String prefix;
private String number;
}
public class HomePhone extends Phone {
}
public class MobilePhone extends Phone {
}
Simple GPS class at the end:
public class Gps {
private double latitude;
private double longitude;
}
For the parsing, we need only 3 things:
- path to XML file which will be used as a data source,
- target object into which the data will be filled in (this is different from the common serialization, where this object is created during deserialization),
- instance of
XmlSerializerclass.
// input file name String FILE_NAME = "C:\\Users\\...\\model2.xml"; // serializer initialization XmlSerializer ser = new XmlSerializer(); // creating target object Database db = new Database(); // and invoking the deserialization ser.fillObject(FILE_NAME, db);
However, in most cases, the serialization process needs to be adjusted. To do so, you will need to create and set settings object and then pass it to a XmlSerializer constructor.
// settings intialization Settings settings = new Settings(); // serializer initialization XmlSerializer ser = new XmlSerializer(settings);In Java, all generic declarations in lists are removed during compilation (Java uses "generic type erasure"). Therefore, it is not possible to detect what is the correct type of the objects loaded into the list. Therefore, you must specify the valid items type for each list. Remember, as all the deserialization is done during run-time, there is no type check and all responsibility is on the programmer. When this mapping is not done, the deserialization will raise an exception. This mapping must be done for all lists in the program.
To specify the mapping between list of some java class and its xml-list-element, you need to create an instance of XmlListMappingClass, like:
XmlListMappingClass map = new XmlListMappingClass(LST_ELM, TYPE)
where
-
LST_ELMis the string representing the label of the node representing list containing elements, -
TYPEis specification of the class (type) which is used to instantiate elements of list.
In our tutorial, the Database class has field-list persons. In XML, this list-field is stored in element database/persons, where each subelement of <persons> represents one item in the list. Using following code we ensure that such subelement will be mapped into Person class:
settings.getListItemMapping().add(
new XmlListItemMapping("persons", Person.class)
);
Exactly to say, in our XML file, all elements inside any "persons" element which is mapped to list will be mapped into Person class.
Sometimes you may need to fill a list with items of different types. In our case, a phone number may be either a mobile phone or home phone. The first point is that those items must differ in element name in the list - see <phones> of the first person in the XML file. Second part follows the previous section - as phones is the list, there must exist a mapping for this list defining which class instances will be used. In our case, there will be two possibilities
- elements named <mobilePhone> will be mapped into
MobilePhoneclass, - elements named <homePhone> will be mapped into
HomePhoneclass.
Therefore we will have to use another overload of the constructor of XmlListItemMaping class by adding middle parameter defining the name of the item-subelement.
settings.getListItemMapping().add(
new XmlListItemMapping("phones", "homePhone", HomePhone.class)
);
settings.getListItemMapping().add(
new XmlListItemMapping("phones", "mobilePhone", MobilePhone.class)
);
In some cases, you may need to define own parsing of some kind of let-say "value-type", like color, date, time, etc., which are typically saved as string attribute (like "2015-04-23" for date) but in the code they are represented by a class.
For this case, when value is set in the xml-attribute or as a value of xml-element, you will need to create custom implementation of IValueParser interface, like:
{
private final java.time.format.DateTimeFormatter formatter;
/**
* Creates instance with custom date-time format pattern.
* @param format
*/
public JavaTimeLocalDateValueParser(String format) {
if (format == null) {
throw new IllegalArgumentException("Value of {format} cannot not be null.");
}
this.formatter = DateTimeFormatter.ofPattern(format);
}
@Override
public String getTypeName() {
return java.time.LocalDate.class.getName();
}
@Override
public LocalDate parse(String value) {
LocalDate ret;
try {
ret =
LocalDate.parse(value, formatter);
} catch (Exception ex){
throw new XmlSerializationException("Failed to parse value '%s' using formatter type '%s' ('%s').",
value,
this.formatter.getClass().getName(),
this.formatter.toString()
);
}
return ret;
}
}
Such class will provide parsing from the String into requested type using parse(..) method. We strongly reccomend to use exception handling during the process for easy debugging!
Then, custom parser must be added into list of custom parsers of the settings object:
settings.getValueParsers().add(
new JavaTimeLocalDateValueParser("yyyy-MM-dd")
);