Problem
I’m currently facing the problem of unmarshalling a huge number (47) of attributes to a Java object. Changing the format of the XML file to be more structured is sadly not an option.
The following should serve as an example. Imagine each request element has 47 attributes instead of two.
<Data time="20161031111103">
<Request name="John Doe" id="123"/>
<Request name="Jane Doe" id="124"/>
</Data>
So far I’ve found three possible solutions, but I’m not really happy with any of them.
Context
The XML file is refreshed minutely on the customers server and then polled and parsed by the client application. I have no influence on how it is generated or formatted.
1 Use a Bean
This is probably the most naive solution, but this gets really boilerplate heavy with more than 600 lines for 47 attributes. On the other hand, this would probably be the easiest to parse.
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
import javafx.beans.property.SimpleStringProperty;
@XmlType(name = "Request")
public class Request implements Serializable {
private final SimpleStringProperty name = new SimpleStringProperty();
private final SimpleStringProperty id = new SimpleStringProperty();
public String getName() {
return name.get();
}
@XmlAttribute(name = "name")
public void setName(String name) {
this.name.set(name);
}
public SimpleStringProperty nameProperty() {
return name;
}
public String getId() {
return id.get();
}
@XmlAttribute(name = "id")
public void setId(String id) {
this.id.set(id);
}
public SimpleStringProperty idProperty() {
return id;
}
}
2 Use a Map with String keys
A bit more manageable boilerplate-wise, but still you’d require 47 constants for all the attribute names and it leaves you inflexible if you’d need something other than a SimpleStringProperty
, a SimpleDoubleProperty
for example.
package xml.model;
import javafx.beans.property.SimpleStringProperty;
import java.util.HashMap;
import java.util.Map;
public class Request2 {
private final Map<String, SimpleStringProperty> properties = new HashMap<>();
private static final String NAME_PROPERTY = "name";
private static final String ID_PROPERTY = "id";
public String getValue(String property) {
SimpleStringProperty result = properties.get(property);
return result == null ? null : result.getValue();
}
public void setValue(String property, String value) {
properties.putIfAbsent(property, new SimpleStringProperty());
properties.get(property).set(value);
}
public SimpleStringProperty valueProperty(String property) {
return properties.get(property);
}
}
3 Use a Map with Enum keys
The same downsides as 2, defining 47 enum constants and being inflexible with other Property types.
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import javafx.beans.property.SimpleStringProperty;
public class Request {
private final Map<Key, SimpleStringProperty> properties = new HashMap<>();
public String getValue(Key property) {
SimpleStringProperty result = properties.get(property);
return result == null ? null : result.getValue();
}
public void setValue(Key property, String value) {
properties.putIfAbsent(property, new SimpleStringProperty());
properties.get(property).set(value);
}
public SimpleStringProperty valueProperty(Key property) {
return properties.get(property);
}
public enum Key {
NAME("name"), ID("id");
private static final Map<String, Key> lookup = Collections
.unmodifiableMap(Arrays.stream(values())
.collect(Collectors.toMap(Key::getName, r -> r)));
private final String name;
Key(String name) {
this.name = name;
}
public static Key get(String name) {
return lookup.get(name);
}
public String getName() {
return this.name;
}
}
}
I feel like the sensible thing would be to spread the attributes to multiple classes, but that would leave me with as much, if not more, boilerplate and would complicate things a fair bit. Is it really in any way “sane” or feasible to have a class with 47 fields like in 1? It just feels super bad.
Solution
I think the best solution is the first one because it exists some tools that are able to automatically generate classes (in bean-style) from XML.
All you need is an XML Schema so that you can use XJC (provided with Java) to let it generate these classes. Of course the generated Request
class will be quite heavy with a lot of boilerplate but since it is done automatically it’s not really a problem…
Going one step further
Since this class will be auto-generated, it means that you must let it untouched. Therefore it means that you can’t use this class as a model (in the MVC sense) because no behaviour can be added to it.
You will have to create your own business model of a Request
and this class can be much better structured. Possible example:
public final class Request {
private final User sender
private final User receiver;
private final Data command;
...
}
With User
and Data
beeing other classes modeling a subpart of a request’s behaviour.
What’s missing now ? A bridge between your business model and the bean. This is where you can use an XmlAdapter to fill this gap. This is basically the glue between the two worlds, able to translate from the business world to the XML world and the opposite.
JAXB class <-> XMLAdapter <-> Business class
What I understand is you wish to avoid writing the variable names, multiple constants.
How about this:
public class Request {
private Map<String, String> attributeValueMap;
public void put(String key, String value){
attributeValueMap.put(key, value);
}
public String put(String key){
attributeValueMap.get(key);
}
}
So, basically you don’t care what the key is. Parse each request and for each Request, create a Request object and populate each of its key-value pair into the map.
Advantage:
- You escape declaring/defining each key.
- No boilerplate code.
- Very generic and is scalable to handle removal or addition of new key.
Disadvantage:
- To access a particular attribute, you need to know the key which basically means it needs to have its presence somewhere.
- It does not guarantee that the key’s would be same for every request’s attribute (which, I guess, would/should be same by default)
Is an XRX solution out of the question?
The basic philosophy here is to write all your business logic in XML based languages (XSLT / XQuery / XForms) and never let the data go anywhere near Java. As soon as you have two different representations of your data (an XML representation and a Java representation) you need an amount of code to translate between the two, where the amount of code is generally proportional to the complexity of the data. This has been called the “impedance mismatch” problem. The way to avoid it is to avoid having multiple representations of the data.
You might be surprised to discover how much more convenient it is to manipulate XML-based data using languages that were designed for the job (which Java wasn’t).