JAXB is a Java binding tool. It generates Java code from a schema and you are able to transform from those classes into XML matching the processed schema and back. Note, that you cannot use your own objects, you have to use what is generated.In this post I will provide an alternative answer to that question.
Java Model
We will use the following model for this example. The classes represent customer data. The get/set methods have been omitted to save space.
Customer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package
comparison;
import
java.util.ArrayList;
import
java.util.List;
public
class
Customer {
private
long
id;
private
String name;
private
Address address;
private
List<phonenumber> phoneNumbers;
public
Customer() {
phoneNumbers =
new
ArrayList<PhoneNumber>();
}
}
|
Address
1
2
3
4
5
6
7
8
|
package
comparison;
public
class
Address {
private
String city;
private
String street;
}
|
PhoneNumber
1
2
3
4
5
6
7
8
|
package
comparison;
public
class
PhoneNumber {
private
String type;
private
String number;
}
|
Customer Data
The following instance of Customer will be marshalled to XML using both JAXB and XStream.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package
comparison;
public
class
Data {
public
static
Customer CUSTOMER;
static
{
CUSTOMER =
new
Customer();
CUSTOMER.setId(
123
);
CUSTOMER.setName(
"Jane Doe"
);
Address address =
new
Address();
address.setStreet(
"1 A Street"
);
address.setCity(
"Any Town"
);
CUSTOMER.setAddress(address);
PhoneNumber workPhoneNumber =
new
PhoneNumber();
workPhoneNumber.setType(
"work"
);
workPhoneNumber.setNumber(
"555-WORK"
);
CUSTOMER.getPhoneNumbers().add(workPhoneNumber);
PhoneNumber cellPhoneNumber =
new
PhoneNumber();
cellPhoneNumber.setType(
"cell"
);
cellPhoneNumber.setNumber(
"555-CELL"
);
CUSTOMER.getPhoneNumbers().add(cellPhoneNumber);
}
}
|
Marshal Code
This is the code we will use to convert the objects to XML.
XStream
The following code will be used to marshal the instance of Customer to an OutputStream. The XStream code is quite compact.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package
comparison.xstream;
import
com.thoughtworks.xstream.XStream;
import
static
comparison.Data.CUSTOMER;
public
class
XStreamDemo {
public
static
void
main(String[] args) {
XStream xstream =
new
XStream();
xstream.autodetectAnnotations(
true
);
xstream.toXML(CUSTOMER, System.out);
}
}
|
JAXB
The following code will be used to marshal the instance of Customer to an OutputStream. A couple of differences are already apparent:
- A JAXBContext needs to be initialized on the binding metadata before the marshal operation can occur.
- Unlike XStream JAXB does not format the XML by default, so we will enable this feature.
- With no metadata specified we need to supply JAXB with a root element name (and namespace).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package
comparison.jaxb;
import
javax.xml.bind.JAXBContext;
import
javax.xml.bind.JAXBElement;
import
javax.xml.bind.Marshaller;
import
javax.xml.namespace.QName;
import
comparison.Customer;
import
static
comparison.Data.CUSTOMER;
public
class
JAXBDemo {
public
static
void
main(String[] args)
throws
Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.
class
);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
true
);
JAXBElement<Customer> jaxbElement =
new
JAXBElement<Customer>(
new
QName(
"customer"
), Customer.
class
, CUSTOMER);
marshaller.marshal(jaxbElement, System.out);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<
customer
>
<
id
>123</
id
>
<
address
>
<
city
>Any Town</
city
>
<
street
>1 A Street</
street
>
</
address
>
<
name
>Jane Doe</
name
>
<
phoneNumbers
>
<
number
>555-WORK</
number
>
<
type
>work</
type
>
</
phoneNumbers
>
<
phoneNumbers
>
<
number
>555-CELL</
number
>
<
type
>cell</
type
>
</
phoneNumbers
>
</
customer
>
|
1
2
3
4
5
|
@XmlAccessorType
(XmlAccessType.FIELD)
package
comparison;
import
javax.xml.bind.annotation.XmlAccessType;
import
javax.xml.bind.annotation.XmlAccessorType;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import
java.util.ArrayList;
import
java.util.List;
import
com.thoughtworks.xstream.annotations.XStreamAlias;
import
com.thoughtworks.xstream.annotations.XStreamImplicit;
@XStreamAlias
(
"customer"
)
public
class
Customer {
private
long
id;
private
String name;
private
Address address;
@XStreamImplicit
(itemFieldName=
"phone-number"
)
private
List<PhoneNumber> phoneNumbers;
public
Customer() {
phoneNumbers =
new
ArrayList<PhoneNumber>();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import
java.util.ArrayList;
import
java.util.List;
import
javax.xml.bind.annotation.XmlElement;
import
javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public
class
Customer {
private
long
id;
private
String name;
private
Address address;
@XmlElement
(name=
"phone-number"
)
private
List<PhoneNumber> phoneNumbers;
public
Customer() {
phoneNumbers =
new
ArrayList<PhoneNumber>();
}
}
|
Note: The JAXB Marshal code can now be simplified since we no longer need to supply the root element information:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package
comparison.jaxb;
import
javax.xml.bind.JAXBContext;
import
javax.xml.bind.Marshaller;
import
comparison.Customer;
import
static
comparison.Data.CUSTOMER;
public
class
JAXBDemo {
public
static
void
main(String[] args)
throws
Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.
class
);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
true
);
marshaller.marshal(CUSTOMER, System.out);
}
}
|
XML Output
At this point the same XML is being produced by XStream and JAXB.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<
customer
>
<
id
>123</
id
>
<
name
>Jane Doe</
name
>
<
address
>
<
city
>Any Town</
city
>
<
street
>1 A Street</
street
>
</
address
>
<
phone-number
>
<
type
>work</
type
>
<
number
>555-WORK</
number
>
</
phone-number
>
<
phone-number
>
<
type
>cell</
type
>
<
number
>555-CELL</
number
>
</
phone-number
>
</
customer
>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package
comparison;
import
java.util.ArrayList;
import
java.util.List;
import
com.thoughtworks.xstream.annotations.XStreamAlias;
import
com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import
com.thoughtworks.xstream.annotations.XStreamImplicit;
@XStreamAlias
(
"customer"
)
public
class
Customer {
@XStreamAsAttribute
private
long
id;
private
String name;
private
Address address;
@XStreamImplicit
(itemFieldName=
"phone-number"
)
private
List<PhoneNumber> phoneNumbers;
public
Customer() {
phoneNumbers =
new
ArrayList<PhoneNumber>();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package
comparison;
import
java.util.ArrayList;
import
java.util.List;
import
javax.xml.bind.annotation.XmlAttribute;
import
javax.xml.bind.annotation.XmlElement;
import
javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public
class
Customer {
@XmlAttribute
private
long
id;
private
String name;
private
Address address;
@XmlElement
(name=
"phone-number"
)
private
List<PhoneNumber> phoneNumbers;
public
Customer() {
phoneNumbers =
new
ArrayList<PhoneNumber>();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<
customer
id
=
"123"
>
<
name
>Jane Doe</
name
>
<
address
>
<
city
>Any Town</
city
>
<
street
>1 A Street</
street
>
</
address
>
<
phone-number
>
<
type
>work</
type
>
<
number
>555-WORK</
number
>
</
phone-number
>
<
phone-number
>
<
type
>cell</
type
>
<
number
>555-CELL</
number
>
</
phone-number
>
</
customer
>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package
comparison;
import
java.util.ArrayList;
import
java.util.List;
import
com.thoughtworks.xstream.annotations.XStreamAlias;
import
com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import
com.thoughtworks.xstream.annotations.XStreamConverter;
import
com.thoughtworks.xstream.annotations.XStreamImplicit;
@XStreamAlias
(
"customer"
)
public
class
Customer {
@XmlAttribute
private
long
id;
private
String name;
private
Address address;
@XStreamImplicit
(itemFieldName=
"phone-number"
)
@XStreamConverter
(PhoneNumberConverter.
class
)
private
List<PhoneNumber> phoneNumbers;
public
Customer() {
phoneNumbers =
new
ArrayList<PhoneNumber>();
}
}
|
The converter can be implemented as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package
comparison;
import
com.thoughtworks.xstream.converters.Converter;
import
com.thoughtworks.xstream.converters.MarshallingContext;
import
com.thoughtworks.xstream.converters.UnmarshallingContext;
import
com.thoughtworks.xstream.io.HierarchicalStreamReader;
import
com.thoughtworks.xstream.io.HierarchicalStreamWriter;
public
class
PhoneNumberConverter
implements
Converter {
public
boolean
canConvert(Class clazz) {
return
PhoneNumber.
class
== clazz;
}
public
void
marshal(Object object, HierarchicalStreamWriter hsw, MarshallingContext mc) {
PhoneNumber phoneNumber = (PhoneNumber) object;
hsw.addAttribute(
"type"
, phoneNumber.getType());
hsw.setValue(phoneNumber.getNumber());
}
public
Object unmarshal(HierarchicalStreamReader hsr, UnmarshallingContext uc) {
PhoneNumber phoneNumber =
new
PhoneNumber();
phoneNumber.setType(hsr.getAttribute(
"type"
));
phoneNumber.setNumber(hsr.getValue());
return
phoneNumber;
}
}
|
JAXB
For JAXB we will use the @XmlAttribute and @XmlValue annotations on the PhoneNumber class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package
comparison;
import
javax.xml.bind.annotation.XmlAttribute;
import
javax.xml.bind.annotation.XmlValue;
public
class
PhoneNumber {
@XmlAttribute
private
String type;
@XmlValue
private
String number;
}
|
XML Output:
The XML output is the same for both JAXB and XStream.
1
2
3
4
5
6
7
8
9
|
<
customer
id
=
"123"
>
<
name
>Jane Doe</
name
>
<
address
>
<
street
>1 A Street</
street
>
<
city
>Any Town</
city
>
</
address
>
<
phone-number
type
=
"work"
>555-WORK</
phone-number
>
<
phone-number
type
=
"cell"
>555-CELL</
phone-number
>
</
customer
>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
package
comparison.xstream;
import
com.thoughtworks.xstream.XStream;
import
com.thoughtworks.xstream.converters.reflection.FieldDictionary;
import
com.thoughtworks.xstream.converters.reflection.SortableFieldKeySorter;
import
com.thoughtworks.xstream.converters.reflection.Sun14ReflectionProvider;
import
com.thoughtworks.xstream.io.xml.QNameMap;
import
com.thoughtworks.xstream.io.xml.StaxDriver;
import
comparison.Address;
import
static
comparison.Data.CUSTOMER;
public
class
XStreamDemo {
public
static
void
main(String[] args) {
QNameMap nsm =
new
QNameMap();
StaxDriver staxDriver =
new
StaxDriver(nsm);
SortableFieldKeySorter sorter =
new
SortableFieldKeySorter();
sorter.registerFieldOrder(Address.
class
,
new
String[] {
"street"
,
"city"
});
FieldDictionary fieldDictionary =
new
FieldDictionary(sorter);
Sun14ReflectionProvider reflectionProvider =
new
Sun14ReflectionProvider(fieldDictionary);
XStream xstream =
new
XStream(reflectionProvider, staxDriver);
xstream.autodetectAnnotations(
true
);
xstream.toXML(CUSTOMER, System.out);
}
}
|
Note: With this change I lost XML formatting, the following was produced.
1
2
|
<
customer
xmlns
=
"http://www.example.com"
id
=
"123"
><
name
>Jane Doe</
name
><
address
><
street
>1 A Street</
street
><
city
>Any Town</
city
></
address
><
phone-number
type
=
"work"
>555-WORK</
phone-number
><
phone-number
type
=
"cell"
>555-CELL</
phone-number
></
customer
>
|
1
2
3
4
5
6
7
8
9
|
@XmlAccessorType
(XmlAccessType.FIELD)
elementFormDefault=XmlNsForm.QUALIFIED)
package
comparison;
import
javax.xml.bind.annotation.XmlAccessType;
import
javax.xml.bind.annotation.XmlAccessorType;
import
javax.xml.bind.annotation.XmlNsForm;
import
javax.xml.bind.annotation.XmlSchema;
|
The following XML was produced
1
2
3
4
5
6
7
8
9
|
<
name
>Jane Doe</
name
>
<
address
>
<
street
>1 A Street</
street
>
<
city
>Any Town</
city
>
</
address
>
<
phone-number
type
=
"work"
>555-WORK</
phone-number
>
<
phone-number
type
=
"cell"
>555-CELL</
phone-number
>
</
customer
>
|