Here's a fairly complete example of using Thoughtworks' XStream library to serialize a POJO to XML, then reverse the process. The very calls into this library are highlighted. The test case here represents the simple, basic uses of XStream to serialize a POJO into XML and deserialize (the same XML string) back into a POJO.
package com.windofkeltia.xstream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import javax.xml.transform.OutputKeys; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.stream.StreamResult; import org.xml.sax.InputSource; import org.junit.Test; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.StaxDriver; public class XStreamTest { @Test public void test() { XStream xstream = new XStream( new StaxDriver() ); Student student = getTestDetails(); // object (POJO) to XML conversion... String xml = xstream.toXML( student ); System.out.println( "Serialization of class Student:\n" + xml ); System.out.println(); System.out.println( "(Pretty-printed:)\n" + formatXml( xml ) ); // XML back to object conversion... Student student1 = ( Student ) xstream.fromXML( xml ); System.out.println( "Deserialization of XML into POJO:\n" + student1 ); } private Student getTestDetails() { Student student = new Student(); student.setFirst( "Lily" ); student.setLast ( "Munster" ); student.setId ( 666 ); student.setYear ( "4th" ); student.setGpa ( 3.43 ); Address address = new Address(); address.setStreet ( "1313 Mockingbird Lane" ); address.setCity ( "Mockingbird Heights" ); address.setState ( "CA" ); address.setCountry( "US" ); address.setZipcode( 90210 ); student.setAddress( address ); return student; } public static String formatXml( String xml ) { try { Transformer serializer = SAXTransformerFactory.newInstance().newTransformer(); serializer.setOutputProperty( OutputKeys.INDENT, "yes" ); serializer.setOutputProperty( "{http://xml.apache.org/xslt}indent-amount", "2" ); Source source = new SAXSource( new InputSource( new ByteArrayInputStream( xml.getBytes() ) ) ); StreamResult res = new StreamResult( new ByteArrayOutputStream() ); serializer.transform( source, res ); // make this prettier... String result = new String( ( ( ByteArrayOutputStream ) res.getOutputStream() ).toByteArray() ); int endOfHeader = result.indexOf( "?>" ) + "?>".length(); return result.substring( 0, endOfHeader ) + '\n' + result.substring( endOfHeader, result.length() ); } catch( Exception e ) { return xml; } } }
Here are the Student and Address POJOs:
package com.windofkeltia.xstream; public class Student { private int id; private String first; private String last; private String year; private Double gpa; private Address address; public int getId() { return id; } public String getFirst() { return first; } public String getLast() { return last; } public String getYear() { return year; } public Double getGpa() { return gpa; } public Address getAddress() { return address; } public void setId ( int id ) { this.id = id; } public void setFirst ( String first ) { this.first = first; } public void setLast ( String last ) { this.last = last; } public void setYear ( String year ) { this.year = year; } public void setGpa ( Double gpa ) { this.gpa = gpa; } public void setAddress( Address address ) { this.address = address; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append( "{\n" ); sb.append( " id: " ).append( id ).append( '\n' ); sb.append( " first: " ).append( first ).append( '\n' ); sb.append( " last: " ).append( last ).append( '\n' ); sb.append( " year: " ).append( year ).append( '\n' ); sb.append( " gpa: " ).append( gpa ).append( '\n' ); sb.append( " address:\n" ).append( address.toString( 1 ) ); sb.append( "}\n" ); return sb.toString(); } }
package com.windofkeltia.xstream; public class Address { private String street; private String city; private String state; private String country; private int zipcode; public String getStreet() { return street; } public String getCity() { return city; } public String getState() { return state; } public String getCountry() { return country; } public int getZipcode() { return zipcode; } public void setStreet( String street ) { this.street = street; } public void setCity( String city ) { this.city = city; } public void setState( String state ) { this.state = state; } public void setCountry( String country ) { this.country = country; } public void setZipcode( int zipcode ) { this.zipcode = zipcode; } public String toString() { return toString( 0 ); } public String toString( int indentLevel ) { StringBuilder sb = new StringBuilder(); String tab = ""; while( indentLevel-- > 0 ) tab += " "; sb.append( tab ).append( "{\n" ); sb.append( tab ).append( " street: " ).append( street ).append( '\n' ); sb.append( tab ).append( " city: " ).append( city ).append( '\n' ); sb.append( tab ).append( " state: " ).append( state ).append( '\n' ); sb.append( tab ).append( " country: " ).append( country ).append( '\n' ); sb.append( tab ).append( " zipcode: " ).append( zipcode ).append( '\n' ); sb.append( tab ).append( "}\n" ); return sb.toString(); } }
Output from this test:
<?xml version="1.0" ?><com.windofkeltia.xstream.Student><id>666</id><first>Lily</first><last>Munster... (Pretty-printed:) <?xml version="1.0" encoding="UTF-8"?> <com.windofkeltia.xstream.Student> <id>666</id> <first>Lily</first> <last>Munster</last> <year>4th</year> <gpa>3.43</gpa> <address> <street>1313 Mockingbird Lane</street> <city>Mockingbird Heights</city> <state>CA</state> <country>US</country> <zipcode>90210</zipcode> </address> </com.windofkeltia.xstream.Student> Deserialization of XML into POJO: { id: 666 first: Lily last: Munster year: 4th gpa: 3.43 address: { street: 1313 Mockingbird Lane city: Mockingbird Heights state: CA country: US zipcode: 90210 } }
<dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.12</version> </dependency>
What if we short the XML input or add to or change it contrary to the POJO? Let's say the XML comes in with Thoughtworks' usually description:
<com.windofkeltia.xstream.Student>
...claiming to be a Student, but missing, adding or exchanging a field. The following examples will help us understand this.
1. Let's test removing completely a field from the XML string. Add this test case and supporting code to XStreamTest.java:
@Test public void testMissingFieldFromOriginalPojo() { XStream xstream = new XStream( new StaxDriver() ); Student student = getTestDetails(); // Object to XML conversion String xml = xstream.toXML( student ); xml = removeStateFieldFromStudent( xml ); System.out.println( "Serialization of class Student:\n" + xml ); System.out.println(); System.out.println( "(Pretty-printed:)\n" + formatXml( xml ) ); // XML to Object conversion Student student1 = ( Student ) xstream.fromXML( xml ); System.out.println( "Deserialization of XML into POJO:\n" + student1 ); } /** * Smoke the state from the address to see what happens. */ private String removeStateFieldFromStudent( final String xml ) { final String STATE_ELEMENT = "<state>CA</state>"; int length = STATE_ELEMENT.length(); int position = xml.indexOf( STATE_ELEMENT ); return xml.substring( 0, position ) + xml.substring( position+length, xml.length() ); }
Now, with the <state> field removed, let's see what the resulting output is. Note that no exception is thrown, the field is missing from the XML string as well as being null in the POJO. Because the POJO's toString() method doesn't guard against null, it prints out that way:
<?xml version="1.0" ?><com.windofkeltia.xstream.Student><id>666</id><first>Lily</first><last>Munster... (Pretty-printed:) <?xml version="1.0" encoding="UTF-8"?> <com.windofkeltia.xstream.Student> <id>666</id> <first>Lily</first> <last>Munster</last> <year>4th</year> <gpa>3.43</gpa> <address> <street>1313 Mockingbird Lane</street> <city>Mockingbird Heights</city> <country>US</country> <zipcode>90210</zipcode> </address> </com.windofkeltia.xstream.Student> Deserialization of XML into POJO: { id: 666 first: Lily last: Munster year: 4th gpa: 3.43 address: { street: 1313 Mockingbird Lane city: Mockingbird Heights state: null country: US zipcode: 90210 } }
2. Next, let's do something more vicious. Not only will we remove the state field, we'll add a new field not defined in the POJO, <province>.
@Test public void testAddingFieldsBeyondOriginalPojo() { XStream xstream = new XStream( new StaxDriver() ); Student student = getTestDetails(); // Object to XML conversion String xml = xstream.toXML( student ); xml = removeStateFieldFromStudent( xml ); xml = addProvinceFieldToStudent( xml ); System.out.println( "Serialization of class Student:\n" + xml ); System.out.println(); System.out.println( "(Pretty-printed:)\n" + formatXml( xml ) ); // XML to Object conversion Student student1 = null; try { student1 = ( Student ) xstream.fromXML( xml ); } catch( Exception e ) { e.printStackTrace(); } System.out.println( "Deserialization of XML into POJO:\n" + student1 ); } /** * Add the province in place of the state in the address to see what happens. */ private String addProvinceFieldToStudent( final String xml ) { final String PROVINCE_ELEMENT = "<province>BC</province>"; final String COUNTRY_ELEMENT = "<country>US</country"; int length = COUNTRY_ELEMENT.length(); int position = xml.indexOf( COUNTRY_ELEMENT ); return xml.substring( 0, position ) + PROVINCE_ELEMENT + xml.substring( position+length+1, xml.length() ); }
<?xml version="1.0" ?><com.windofkeltia.xstream.Student><id>666</id><first>Lily</first><last>Munster...
(Pretty-printed:)
<?xml version="1.0" encoding="UTF-8"?>
<com.windofkeltia.xstream.Student>
<id>666</id>
<first>Lily</first>
<last>Munster</last>
<year>4th</year>
<gpa>3.43</gpa>
<address>
<street>1313 Mockingbird Lane</street>
<city>Mockingbird Heights</city>
<province>BC</province>
<zipcode>90210</zipcode>
</address>
</com.windofkeltia.xstream.Student>
com.thoughtworks.xstream.converters.ConversionException: province : province
---- Debugging information ----
message : province : province
cause-exception : com.thoughtworks.xstream.mapper.CannotResolveClassException
cause-message : province : province
class : com.windofkeltia.xstream.Student
required-type : com.windofkeltia.xstream.Address
path : /com.windofkeltia.xstream.Student/address/province
line number : 1
-------------------------------
at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:63)
at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:45)
at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:46)
at com.thoughtworks.xstream.annotations.AnnotationReflectionConverter.unmarshallField(AnnotationReflectionConverter.java:66)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:188)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:125)
at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:56)
at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:45)
at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:46)
at com.thoughtworks.xstream.core.TreeUnmarshaller.start(TreeUnmarshaller.java:117)
at com.thoughtworks.xstream.core.ReferenceByXPathMarshallingStrategy.unmarshal(ReferenceByXPathMarshallingStrategy.java:29)
at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:846)
at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:833)
at com.thoughtworks.xstream.XStream.fromXML(XStream.java:781)
at com.thoughtworks.xstream.XStream.fromXML(XStream.java:773)
at com.windofkeltia.xstream.XStreamTest.testAddingFieldsBeyondOriginalPojo(XStreamTest.java:120)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: com.thoughtworks.xstream.mapper.CannotResolveClassException: province : province
at com.thoughtworks.xstream.mapper.DefaultMapper.realClass(DefaultMapper.java:49)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
at com.thoughtworks.xstream.mapper.ClassAliasingMapper.realClass(ClassAliasingMapper.java:76)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
at com.thoughtworks.xstream.mapper.DynamicProxyMapper.realClass(DynamicProxyMapper.java:60)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
at com.thoughtworks.xstream.mapper.ArrayMapper.realClass(ArrayMapper.java:76)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
at com.thoughtworks.xstream.mapper.CachingMapper.realClass(CachingMapper.java:34)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.determineType(AbstractReflectionConverter.java:296)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:178)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:125)
at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:56)
Deserialization of XML into POJO:
null
3. Last, let's just add <province> without removing the state field. The result is similar to the last case, the exception is thrown. You see this
<?xml version="1.0" ?><com.windofkeltia.xstream.Student><id>666</id><first>Lily</first><last>Munster...
(Pretty-printed:)
<?xml version="1.0" encoding="UTF-8"?>
<com.windofkeltia.xstream.Student>
<id>666</id>
<first>Lily</first>
<last>Munster</last>
<year>4th</year>
<gpa>3.43</gpa>
<address>
<street>1313 Mockingbird Lane</street>
<city>Mockingbird Heights</city>
<state>CA</state>
<province>BC</province>
<zipcode>90210</zipcode>
</address>
</com.windofkeltia.xstream.Student>
(exception here just as before)
Leave fields out of the serialized XML, no problem—they'll be shown to be unset in the deserialized POJO and, depending on how crucial they are, you'll need to check them before use.
Add a new field that's not in the POJO will result in the code throwing an exception when deserialized from XML (fromXML() method).