5
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ Did you use a tool to generate Request or you wrote it manually ? \$\endgroup\$ – Spotted Nov 14 '16 at 8:02
  • \$\begingroup\$ @Spotted I wouldn't know, it is provided as is. \$\endgroup\$ – Marv Nov 14 '16 at 8:08
  • \$\begingroup\$ If your concerns are about design and modelling and not about performances, you could consider the the XLST transformation to split up the first xml with a lot of attributes in a different xml more manageable by proper beans. But this really depends on what you are doing with this xml. \$\endgroup\$ – Mario Santini Nov 14 '16 at 8:14
  • \$\begingroup\$ What do you mean by it is provided as is. ? What is provided and what did you wrote yourself actually ? \$\endgroup\$ – Spotted Nov 14 '16 at 8:21
  • \$\begingroup\$ @MarioSantini I will look into that. \$\endgroup\$ – Marv Nov 14 '16 at 8:46
2
\$\begingroup\$

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

|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ My experience of projects that used the JAXB approach is that it works very well if the schema is very simple and very stable, but it becomes a nightmare if the schema is complex or if there are frequent changes. \$\endgroup\$ – Michael Kay Nov 14 '16 at 9:58
  • \$\begingroup\$ @MichaelKay Indeed, I also experienced that. However I don't know if it is also true for the OP (that the schema is likely to change). \$\endgroup\$ – Spotted Nov 14 '16 at 10:01
1
\$\begingroup\$

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)
|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ Yeah well this is pretty much my second Idea minus those name constants which are needed for attribute retrieval. You wouldn't want to have those Strings spread over the whole application. \$\endgroup\$ – Marv Nov 14 '16 at 21:39
0
\$\begingroup\$

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).

|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ Maybe I'm misunderstanding you, but the data needs to be displayed in the clients UI (JavaFX). The data is polled once a minute from the server and then frequently read. \$\endgroup\$ – Marv Nov 14 '16 at 10:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.