Recall that the B in JAX-B stands for data binding, the associating of a Java data type such as String
to
an XML Schema (or equivalent) type, in this case xsd:string
. There are built-in bindings for the Java
primitive types such as int
and double
together with String
and Calendar
; arrays (including Collections
) of any such
types; and programmer-defined types that reduce, via properties, to any of the preceding. The surprising omission is the
Map
, a collection of key/value pairs, but a Map
is readily handled as two coordinated collections: a collection of
keys and a corresponding collection of values. An example of JAX-B in action may help to drive these points
home.
The Skier
class (see Example 3-4) is annotated with @XmlRootElement
to inform the JAX-B utilities that
a Skier
instance should be
transformed into an XML document that has skier
as its root or document
(that is, outermost) element. In the default Java naming convention, the
root element is the lowercase version of the class name; hence, Skier
becomes skier
. The annotation could be amended:
@XmlRootElement
(
name
=
"NordicSkier"
)
so that the root element has a specified name, in this example NordicSkier
.
Example 3-4. The annotated Skier
POJO class
import
javax.xml.bind.annotation.XmlRootElement
;
import
java.util.Collection
;
@XmlRootElement
public
class
Skier
{
private
Person
person
;
private
String
nationalTeam
;
private
Collection
majorAchievements
;
public
Skier
()
{
}
// required for unmarshaling
public
Skier
(
Person
person
,
String
nationalTeam
,
Collection
<
String
>
majorAchievements
)
{
setPerson
(
person
);
setNationalTeam
(
nationalTeam
);
setMajorAchievements
(
majorAchievements
);
}
// properties
public
Person
getPerson
()
{
return
this
.
person
;
}
public
void
setPerson
(
Person
person
)
{
this
.
person
=
person
;
}
public
String
getNationalTeam
()
{
return
this
.
nationalTeam
;
}
public
void
setNationalTeam
(
String
nationalTeam
)
{
this
.
nationalTeam
=
nationalTeam
;
}
public
Collection
getMajorAchievements
()
{
return
this
.
majorAchievements
;
}
public
void
setMajorAchievements
(
Collection
majorAchievements
)
{
this
.
majorAchievements
=
majorAchievements
;
}
}
The Skier
class has a property of programmer-defined type Person
(see Example 3-5), which
in turn is a POJO class with three properties: name
, age
, and gender
.
Two of the Person
properties are of Java type String
, which binds to
XML type xsd:string
. The third Person
property is of Java type
int
, which binds to the XML type xsd:int
.
Example 3-5. The annotated Person
POJO class with three properties
import
javax.xml.bind.annotation.XmlType
;
@XmlType
public
class
Person
{
private
String
name
;
private
int
age
;
private
String
gender
;
public
Person
()
{
}
public
Person
(
String
name
,
int
age
,
String
gender
){
setName
(
name
);
setAge
(
age
);
setGender
(
gender
);
}
public
String
getName
()
{
return
name
;
}
public
void
setName
(
String
name
)
{
this
.
name
=
name
;
}
public
int
getAge
()
{
return
age
;
}
public
void
setAge
(
int
age
)
{
this
.
age
=
age
;
}
public
String
getGender
()
{
return
gender
;
}
public
void
setGender
(
String
gender
)
{
this
.
gender
=
gender
;
}
}
The annotation XmlType
declares that a Person
instance can be transformed into
an XML type, a type that an XML Schema specifies in detail. The upshot of the
annotated Skier
and Person
classes is this: an in-memory Skier
object, which
encapsulates a Person
, can be transformed into a single XML document, whose
root element is tagged skier
, and the skier
document encapsulates a person
element.
The application class Marshal
(see Example 3-6) does the following:
Recall that, in this context, marshaling is the process of serializing an in-memory object into an XML document; unmarshaling is the inverse process of creating an in-memory object from an XML document.
Example 3-6. The Marshal
application that marshals and unmarshals a Skier
import
java.io.File
;
import
java.io.OutputStream
;
import
java.io.FileOutputStream
;
import
java.io.InputStream
;
import
java.io.FileInputStream
;
import
java.io.IOException
;
import
javax.xml.bind.JAXBContext
;
import
javax.xml.bind.Marshaller
;
import
javax.xml.bind.Unmarshaller
;
import
javax.xml.bind.JAXBException
;
import
java.util.List
;
import
java.util.ArrayList
;
class
Marshal
{
private
static
final
String
fileName
=
"bd.mar"
;
public
static
void
main
(
String
[
]
args
)
{
new
Marshal
().
runExample
();
}
private
void
runExample
()
{
try
{
JAXBContext
ctx
=
JAXBContext
.
newInstance
(
Skier
.
class
);
![]()
Marshaller
m
=
ctx
.
createMarshaller
();
![]()
m
.
setProperty
(
Marshaller
.
JAXB_FORMATTED_OUTPUT
,
true
);
// Marshal a Skier object: 1st to stdout, 2nd to file
Skier
skier
=
createSkier
();
m
.
marshal
(
skier
,
System
.
out
);
FileOutputStream
out
=
new
FileOutputStream
(
fileName
);
m
.
marshal
(
skier
,
out
);
![]()
out
.
close
();
// Unmarshal as proof of concept
Unmarshaller
u
=
ctx
.
createUnmarshaller
();
![]()
Skier
bdClone
=
(
Skier
)
u
.
unmarshal
(
new
File
(
fileName
));
System
.
out
.
println
();
m
.
marshal
(
bdClone
,
System
.
out
);
}
catch
(
JAXBException
e
)
{
System
.
err
.
println
(
e
);
}
catch
(
IOException
e
)
{
System
.
err
.
println
(
e
);
}
}
private
Skier
createSkier
()
{
Person
bd
=
new
Person
(
"Bjoern Daehlie"
,
49
,
"Male"
);
List
<
String
>
list
=
new
ArrayList
<
String
>();
list
.
add
(
"12 Olympic Medals"
);
list
.
add
(
"9 World Championships"
);
list
.
add
(
"Winningest Winter Olympian"
);
list
.
add
(
"Greatest Nordic Skier"
);
return
new
Skier
(
bd
,
"Norway"
,
list
);
}
}
In the Marshal
class, the critical step is the creation of a JAXBContext
(line 1), in this
case a structure built from Java reflection on the type Skier
. (The one-argument method newInstance
can take, as its argument, a single class or a package identifier.) The utility class JAXBContext
then
guides the marshaling and unmarshaling: the Marshaller
and the Unmarshaller
are created
with JAXBContext
methods (lines 2 and 4). By the way, there is no agreement about whether
marshaling and unmarshaling should be spelled with one l or two.
The marshaling (line 3) produces an XML document (see Example 3-7) that serves as the source of the
unmarshaling (line 4). The only complexity is in the elements tagged majorAchievement
, which include
three attributes apiece. The reason is that a major
Achievement
is, in Java, a
Collection
type—in particular, an ArrayList<String>
. The corresponding XML type is
an array of xs:string
objects; the majorAchievement
elements cite the
XML Schema grammar (lines 1 and 2), which includes rules about arrays. By default, the Java
marshaling produces an XML document in which the properties of the source Java object
are in alphabetical order.
Example 3-7. The XML document generated from marshaling a sample Skier
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
standalone
=
"yes"
?>
<
skier
>
<
majorAchievements
xmlns:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
![]()
xmlns:
xs
=
"http://www.w3.org/2001/XMLSchema"
![]()
xsi:
type
=
"xs:string"
>
12
Olympic
Medals
</
majorAchievements
>
<
majorAchievements
xmlns:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:
xs
=
"http://www.w3.org/2001/XMLSchema"
xsi:
type
=
"xs:string"
>
9
World
Championships
</
majorAchievements
>
<
majorAchievements
xmlns:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:
xs
=
"http://www.w3.org/2001/XMLSchema"
xsi:
type
=
"xs:string"
>
Winningest
Winter
Olympian
</
majorAchievements
>
<
majorAchievements
xmlns:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:
xs
=
"http://www.w3.org/2001/XMLSchema"
xsi:
type
=
"xs:string"
>
Greatest
Nordic
Skier
</
majorAchievements
>
<
nationalTeam
>
Norway
</
nationalTeam
>
<
person
>
<
age
>
49
</
age
>
<
gender
>
Male
</
gender
>
<
name
>
Bjoern
Daehlie
</
name
>
</
person
>
</
skier
>
This JAX-B example implies, of course, that the conversion between Java and XML could be automated. Accordingly, a Java client against a RESTful service could:
In this scenario, the XML is transparent. The second Amazon example goes into the details of how this can be done. First, however, an alternative to the standard Java XML utilities deserves a look.
Java comes with standard packages for Java-to-XML conversions and XML-to-JSON conversions. There are also
various contributed libraries, among them XStream. This section examines the
XStream option for serializing Java objects to XML/JSON documents and deserializing Java objects from
such documents. XStream includes a persistence API and has extensions in support of the Hibernate ORM (Object
Relation Mapper). Among the more interesting features of XStream is that its API does not center on the
get/set methods that define Java properties. XStream can serialize into XML an instance of a Java class that
has nothing but private
fields. XStream emphasizes its ease of use, which the following examples try to capture.
The PersonNoProps
class (see Example 3-8) illustrates the ease of XStream
use. The class has three
private
fields and only a three-argument constructor; of interest is that the class has
no properties—no public
get/set
methods. Nonetheless, an instance of the PersonNoProps
class can be serialized or deserialized straightforwardly. Line 1 in the code listing constructs an
XStream
instance with a DOM driver. Line 2 provides an alias for the document element’s
tag, in this case skier
. If an alias were not provided, then the document element would have a tag
named after the class; hence, line 2 is optional. The serialization in line 3 and the
deserialization in line 4 are quick and easy.
Example 3-8. An XStream example with a class that has no properties
import
com.thoughtworks.xstream.XStream
;
import
com.thoughtworks.xstream.io.xml.DomDriver
;
public
class
PersonNoProps
{
private
String
name
;
private
int
age
;
private
String
gender
;
public
PersonNoProps
(
String
name
,
int
age
,
String
gender
){
this
.
name
=
name
;
this
.
age
=
age
;
this
.
gender
=
gender
;
}
}
class
Main
{
public
static
void
main
(
String
[
]
args
)
{
PersonNoProps
bd
=
new
PersonNoProps
(
"Bjoern Daehlie"
,
49
,
"Male"
);
// setup
XStream
xstream
=
new
XStream
(
new
DomDriver
());
![]()
xstream
.
alias
(
"skier"
,
PersonNoProps
.
class
);
// for readability
![]()
// serialize
String
xml
=
xstream
.
toXML
(
bd
);
![]()
System
.
out
.
println
(
xml
);
// deserialize and confirm
PersonNoProps
bdClone
=
(
PersonNoProps
)
xstream
.
fromXML
(
xml
);
![]()
System
.
out
.
println
(
xstream
.
toXML
(
bdClone
));
}
}
The output for both println
calls is:
<
skier
>
<
name
>
Bjoern
Daehlie
</
name
>
<
age
>
49
</
age
>
<
gender
>
Male
</
gender
>
</
skier
>
The generated XML is minimalist. Compiling and running this code requires the core XStream
packages,
which come in a single JAR file: xstream.jar.
The first XStream
example begins with the serialization of an entire PersonNoProps
instance and
ends with the deserialization of a clone. XStream
also supports selective or fine-grained
serialization and deserialization. The next example (see Example 3-9) illustrates this.
Example 3-9. The PersonPropsConverter
class for customized marshaling
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
PersonPropsConverter
implements
Converter
{
public
boolean
canConvert
(
Class
c
)
{
return
c
.
equals
(
PersonProps
.
class
);
![]()
}
// As proof of concept, marshal/unmarshal only the name.
public
void
marshal
(
Object
object
,
HierarchicalStreamWriter
writer
,
MarshallingContext
context
)
{
PersonProps
person
=
(
PersonProps
)
object
;
writer
.
startNode
(
"Person"
);
writer
.
setValue
(
person
.
getName
());
![]()
writer
.
endNode
();
}
public
Object
unmarshal
(
HierarchicalStreamReader
reader
,
UnmarshallingContext
context
)
{
PersonProps
person
=
new
PersonProps
();
reader
.
moveDown
();
person
.
setName
(
reader
.
getValue
());
![]()
reader
.
moveUp
();
return
person
;
}
}
The PersonPropsConverter
class (see Example 3-9) serializes and deserializes, as proof of concept, only one
property in a PersonProps
instance: the name
property. An implementation of the Converter
interface
must define three methods:
canConvert
boolean
to indicate which types are eligible for the customized
serialization and deserialization defined in the methods marshal
and unmarshal
(lines 2 and 3,
respectively). In this example, an object must be of type PersonProps
(see Example 3-10), which includes any descendants of this
class, in order to be convertible.
marshal
PersonProps
object. In
the current example, only the person’s name
property is serialized, but any subset of the properties, including
all of them, could be serialized.
unmarshal
marshal
, supports customized deserialization (unmarshaling).
Example 3-10. The code to illustrate customized XStream marshaling/unmarshaling
import
com.thoughtworks.xstream.XStream
;
import
com.thoughtworks.xstream.io.xml.DomDriver
;
public
class
PersonProps
{
private
String
name
;
private
int
age
;
private
String
gender
;
// constructor
public
PersonProps
()
{
}
// properties
public
void
setName
(
String
name
)
{
this
.
name
=
name
;
}
![]()
public
String
getName
()
{
return
this
.
name
;
}
public
void
setAge
(
int
age
)
{
this
.
age
=
age
;
}
![]()
public
int
getAge
()
{
return
this
.
age
;
}
public
void
setGender
(
String
gender
)
{
this
.
gender
=
gender
;
}
![]()
public
String
getGender
()
{
return
this
.
gender
;
}
}
class
Main
{
public
static
void
main
(
String
[
]
args
)
{
// Create a person and set only the name.
PersonProps
person
=
new
PersonProps
();
person
.
setName
(
"Bruno"
);
XStream
xstream
=
new
XStream
(
new
DomDriver
());
xstream
.
registerConverter
(
new
PersonPropsConverter
());
![]()
xstream
.
alias
(
"name"
,
PersonProps
.
class
);
String
xml
=
xstream
.
toXML
(
person
);
![]()
System
.
out
.
println
(
xml
);
PersonProps
clone
=
(
PersonProps
)
xstream
.
fromXML
(
xml
);
![]()
System
.
out
.
println
(
clone
.
getName
());
// Bruno
}
}
The PersonProps
class (see Example 3-10) revises the PersonNoProps
class by adding properties.
The revised class has
three conventional Java properties, each defined as a pair of get/set
methods. The
properties are name
(type String
) in line 1, age
(type int
) in line 2, and gender
(type String
)
in line 3. The
revised class has a no-argument constructor.
The tester class Main
creates an XStream
instance as before but now registers a
customized Converter
(line 4), a PersonPropsConverter
(see Example 3-9). In the
call to toXML
(line 5), the customized converter takes over and serializes only the
name
property. The output is:
<
person
>
<
name
>
Bruno
</
name
>
</
person
>
The deserialization (line 6) creates a new PersonProps
instance and sets the name
property
to Bruno
. The other properties, age
and gender
, have the default values for fields, in
this case 0
and null
, respectively.
The core XStream
library also supports the conversion of Java objects to and from JSON. There
are various JSON drivers available in this library, the simplest of which is the
JsonHierarchicalStreamDriver
. This driver supports the serialization of Java objects to JSON
but not the inverse operation. If deserialization from JSON to Java is needed, then a driver such
as Jettison is a good choice because it interoperates cleanly with
XStream
.
The JsonTest
code (see Example 3-11) illustrates basic JSON serialization in XStream
. An XStream
instance is now constructed with a JSON driver (line 1), in this case an instance of
a JsonHierarchicalStreamDriver
, which comes with the core XStream
JAR file.
The serializing method is still named toXML
(line 2), but the output is JSON rather
than XML because of the JSON driver.
Example 3-11. An example of XStream serialization to JSON
import
com.thoughtworks.xstream.XStream
;
import
com.thoughtworks.xstream.io.xml.DomDriver
;
import
com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver
;
public
class
JsonTest
{
public
static
void
main
(
String
[
]
args
)
{
PersonNoProps
bd
=
new
PersonNoProps
(
"Bjoern Daehlie"
,
49
,
"Male"
);
XStream
xstream
=
new
XStream
(
new
JsonHierarchicalStreamDriver
());
![]()
String
json
=
xstream
.
toXML
(
bd
);
// it's really toJson now
![]()
System
.
out
.
println
(
json
);
}
}
Here is the output:
{
"PersonNoProps"
:
{
"name"
:
"Bjoern Daehlie"
,
"age"
:
49
,
"gender"
:
"Male"
}}
XStream
supports customized JSON serialization. For example, a programmer might not want
the root element PersonNoProps
included in the JSON, and the JSON serializer can be
programmed to exclude this element.
The XStream
API is remarkably low fuss but likewise powerful. This API has gained steadily in
popularity among Java developers who are looking for quick and easy ways to convert between Java objects on the
one side and either XML or JSON documents on the other side.
The JAX-B and XStream
examples illustrate serialization from Java to XML or JSON and deserialization from
XML or JSON to Java. In the context of clients against RESTful web services, the deserialization side of
the coin is of primary interest because these clients need to process the response payloads, in XML or JSON,
that come from the RESTful service. Accordingly, the next section returns to Amazon’s E-Commerce service but
this time with the goal of hiding the XML that this service returns in response to a successful HTTP request.