Tutorial

The source code for this short tutorial is stored within a sample project that be found here.

Business Objects

This tutorial (de)serialzes some simple business objects.

Car

Each car has several fields that contain other objects (the base price, the model, color and a list of extras). Those are stored within fields

public class Car {
  private final Model model;
  private final Color color;
  private final Money basePrice;
  private final List<Extra> extras = new ArrayList<Extra>();

  public Car( Model model, Color color, Money basePrice ) {
    this( model, color, basePrice, null );
  }

  public Car( Model model, Color color, Money basePrice, Collection<? extends Extra> extras ) {
    this.model = model;
    this.color = color;
    this.basePrice = basePrice;

    if ( extras != null ) {
      this.extras.addAll( extras );
    }
  }

Money

The money class simply holds the value in cents.

public class Money {
  private int cents;

  public Money( int dollars, int cents ) {
    this.cents = dollars * 100 + cents;
  }

  public Money( int cents ) {
    this.cents = cents;
  }

The money object is referenced at two places. At first each Car has a field of type Money representing the base price. Additionally every Extra has its own price.

Model

The model simply consists of a single string that describes the model.

public class Model {
  private final String name;

  public Model( String name ) {
    this.name = name;
  }

Extra

Each extra has a description (String) and a price (Money).

public class Extra {
  private final String description;
  private final Money price;

  public Extra( String description, Money price ) {
    this.description = description;
    this.price = price;
  }

Serializing strategy

At first it seems to be the simplest solution to just create a CarSerializers that does all the work. We could create methods for each type (Money is referenced from Car and Extra) to avoid code duplication.

This is not the recommended solution.

Best practice for improved testability and reusability

For improved testability and reusability it is suggested to create one serializer for each object. Those serializers can be (re)used as delegates. (see Best Practices for details).

To allow delegation of serialization the interface com.cedarsoft.serialization.PluggableSerializer has been introduced. All abstract base classes implement this interface - so no additional work has to be done.

Where to begin?

Because the serializers of the "bigger" objects delegate parts of the serialization to other serializers it is suggested to start the implementation beginning with the serializers for the "smaller" leaf objects (often value objects).

In our example it might be a good idea to implement the serializers for the Model objects first (we could also have chosen Money).

ModelSerializer

The Model object can be serialized easily. There is just one field that has be serialized (String name).

We chose to serialize to XML using Stax Mate. Therefore our serializers should extend AbstractStaxMateSerializer.

The Constructor

The constructor is used to add the necessary meta information. That information is stored within final fields of the abstract base class and used where necessary.

The parameters are:

  • String defaultElementName: The name for the root element, if this serializers is not used as delegate. For delegating serializers that value is not used.
  • String nameSpaceBase: The nameSpaceBase is extended with the version number represents the name space within the xml.
  • VersionRange formatVersionRange: The supported format version range. The upper bound represents the format that is written. All Versions within the range can be read.
      public ModelSerializer() {
        super( "model", "http://thecompany.com/test/model", new VersionRange( new Version( 1, 0, 0 ), new Version( 1, 0, 0 ) ) );
      }
    

Serialization method

Serialization is delegated to the method serialize. This method is used either the serializer is used alone or as delegate. Just implement the abstract method:

The parameters of this method are:

  • SMOutputElement serializeTo: The output element the serialized content is written to. Because we use Stax Mate as backend, this parameter has the type SMOutputElement.
  • Model object the object to serialize.
      @Override
      public void serialize( SMOutputElement serializeTo, Model object, Version formatVersion ) throws IOException, XMLStreamException {
        assert isVersionWritable( formatVersion );
        serializeTo.addCharacters( object.getName() );
      }
    

    We just add the name of the model as text to the current xml tag (that has automatically been created). Therefore the resulting XML looks like that:

    <?xml version="1.0" encoding="UTF-8"?>
    <model xmlns="http://thecompany.com/test/model/1.0.0">Toyota</model>

    The namespace declaration (starting with "xmlns") contains the information about the format version. That information is parsed and used to verify whether the serializer is able to deserialize the object.

Deserialization method

Deserialization is done accordingly within the deserialize method which has two parameters:

  • XMLStreamReader deserializeFrom: The object the serialized object is read from. We use a stax based serializer, therefore this is of type XMLStreamReader
  • Version formatVersion the format version. Serializers may support multiple versions, therefore that parameter can be used to distinguish between different strategies.
      @Override
      public Model deserialize( XMLStreamReader deserializeFrom, Version formatVersion ) throws IOException, XMLStreamException {
        assert isVersionReadable( formatVersion );
        return new Model( getText( deserializeFrom ) );
        //getText automatically closes the tag
      }
    }
    

    There exist several helper methods to work with the XMLStreamReader object. The getText method is used to get the characters. The deserialize method returns a newly created model object.

MoneySerializer

The serializer for the Money objects is the next one. The Money object also contains just one field (int cents). It basically works in the same way as the ModelSerializer does. The cents are added as text to the precreated tag.

public class MoneySerializer extends AbstractStaxMateSerializer<Money> {
  public MoneySerializer() {
    super( "money", "http://thecompany.com/test/money", new VersionRange( new Version( 1, 0, 0 ), new Version( 1, 0, 0 ) ) );
  }

  @Override
  public void serialize( SMOutputElement serializeTo, Money object, Version formatVersion ) throws IOException, XMLStreamException {
    assert isVersionWritable( formatVersion );
    serializeTo.addCharacters( String.valueOf( object.getCents() ) );
  }

  @Override
  public Money deserialize( XMLStreamReader deserializeFrom, Version formatVersion ) throws IOException, XMLStreamException {
    assert isVersionReadable( formatVersion );
    int cents = Integer.parseInt( getText( deserializeFrom ) );

    //We don't have to close the tag. The getText method does that for us
    return new Money( cents );
  }
}

This serializer is modified later to show how refactorings and/or format changes are supported. Those changes are available in MoneySerializer2. Of course this name has only be chosen to be able to add both versions of the MoneySerialzer to the project. In a real world project the MoneySerializer is changed.

ExtraSerializer

The ExtraSerializer is a serializer that uses another serializer as delegate. The Extra contains two fields that shall be serialized:

public class Extra {
  private final String description;
  private final Money price;

  public Extra( String description, Money price ) {
    this.description = description;
    this.price = price;
  }

We could either serialize the money objects on our own. But that added duplicate code! Therefore we reuse the MoneySerializer and delegate the (de)serialization of the Money object.

Constructor

The constructor is a little bit more complex, since the delegate for the serialization of the money field is added and configured.

  public ExtraSerializer( MoneySerializer moneySerializer ) {
    super( "extra", "http://thecompany.com/test/extra", new VersionRange( new Version( 1, 5, 0 ), new Version( 1, 5, 0 ) ) );
    //We choose another version number. Maybe this is an old serializer that has been created within another project.

    add( moneySerializer ).responsibleFor( Money.class )
      .map( 1, 5, 0 ).toDelegateVersion( 1, 0, 0 )
    ;

    //Verify the delegate mappings
    //This is necessary, to ensure that the file format for the
    //object stays constant.
    //If someone changes the MoneySerializer (and increases the version number), this step
    //enforces us to take the necessary steps to handle that situation:
    //Either increase the version number of this serializer (recommended)
    //or handle the differences with some magic (may be necessary sometimes - but generally not recommended)
    assert getDelegatesMappings().verify();
  }

This serializers supports a different version range as the other serializers (1.5.0-1.5.0). The version numbers of the serializers are independent from each other - even when they are used as delegates.

Today - in the time of Dependency Injection - we chose to add the money serializer as constructor parameter. That serializer is added to the delegates mappings.

The last line within the constructor verifies the version ranges of the MoneySerializer fits the expected one. This is necessary to ensure that the format for this serializer stays constant. If the format for Money is changed, the format for Extra is affected, too. Too ensure that the format version is updated accordingly, it is necessary to verify the version range of your delegating objects.

Just call verify on the DelegatesMappings object. It is good practice to verify every serializer with delegates. To improve performance in production environments, it is suggested to call this method only when assertions are enabled.

(De)serialization

Serialization is quite easy. We add an element "description" containing the description of the Extra as text. Then the price is written to its own tag using the MoneySerializer.

  @Override
  public void serialize( SMOutputElement serializeTo, Extra object, Version formatVersion ) throws IOException, XMLStreamException {
    assert isVersionWritable( formatVersion );
    serializeTo.addElement( serializeTo.getNamespace(), "description" ).addCharacters( object.getDescription() );

    //We delegate the serialization of the price to the money serializer
    serialize( object.getPrice(), Money.class, serializeTo.addElement( serializeTo.getNamespace(), "price" ), formatVersion );
  }

  @Override
  public Extra deserialize( XMLStreamReader deserializeFrom, Version formatVersion ) throws IOException, XMLStreamException {
    assert isVersionReadable( formatVersion );
    String description = getChildText( deserializeFrom, "description" );

    nextTag( deserializeFrom, "price" );
    Money price = deserialize( Money.class, formatVersion, deserializeFrom );
    //closes the price tag automatically

    //we have to close our tag now
    closeTag( deserializeFrom );

    return new Extra( description, price );
  }

The deserialization is also very easy and should be self explaining.

Serialized XML

The serialized XML looks like that:

<?xml version="1.0" encoding="UTF-8"?>
<extra xmlns="http://thecompany.com/test/extra/1.5.0">
  <description>Metallic</description>
  <price>40000</price>
</extra>

CarSerializer

Finally we have to create the CarSerializer. Each Car contains several other objects:

public class Car {
  private final Model model;
  private final Color color;
  private final Money basePrice;
  private final List<Extra> extras = new ArrayList<Extra>();

  public Car( Model model, Color color, Money basePrice ) {
    this( model, color, basePrice, null );
  }

  public Car( Model model, Color color, Money basePrice, Collection<? extends Extra> extras ) {
    this.model = model;
    this.color = color;
    this.basePrice = basePrice;

    if ( extras != null ) {
      this.extras.addAll( extras );
    }
  }

Since we have created serializers for (nearly) all objects the car has references to, the CarSerializer is also quite simple and straight forward to implement.

Constructor for CarSerializer

The constructor contains three delegates and the other meta informations that are necessary. The delegates may have different version ranges (the ExtraSerializer supports only 1.5.0).

  public CarSerializer( MoneySerializer moneySerializer, ExtraSerializer extraSerializer, ModelSerializer modelSerializer ) {
    super( "car", "http://thecompany.com/test/car", VersionRange.from( 1, 0, 0 ).to( 1, 0, 0 ) );

    add( moneySerializer ).responsibleFor( Money.class )
      .map( 1, 0, 0 ).toDelegateVersion( 1, 0, 0 );

    add( extraSerializer ).responsibleFor( Extra.class )
      .map( 1, 0, 0 ).toDelegateVersion( 1, 5, 0 );

    add( modelSerializer ).responsibleFor( Model.class )
      .map( 1, 0, 0 ).toDelegateVersion( 1, 0, 0 );

    //Verify the delegate mappings
    assert getDelegatesMappings().verify();
  }

The delegates mappings are verified to ensure that the version (ranges) match.

Serialization of a Car

The serialization method delegates the serialization. In most serializers nearly all the work is done within serializers.

The CarSerializer serializes the color itself. This should be replaced with delegation to a ColorSerializer.

  @Override
  public void serialize( SMOutputElement serializeTo, Car object, Version formatVersion ) throws IOException, XMLStreamException {
    assert isVersionWritable( formatVersion );

    SMOutputElement colorElement = serializeTo.addElement( serializeTo.getNamespace(), "color" );  //okay, should be a own serializer in real world...
    colorElement.addAttribute( "red", String.valueOf( object.getColor().getRed() ) );
    colorElement.addAttribute( "blue", String.valueOf( object.getColor().getBlue() ) );
    colorElement.addAttribute( "green", String.valueOf( object.getColor().getGreen() ) );

    serialize( object.getModel(), Model.class, serializeTo.addElement( serializeTo.getNamespace(), "model" ), formatVersion );
    serialize( object.getBasePrice(), Money.class, serializeTo.addElement( serializeTo.getNamespace(), "basePrice" ), formatVersion );


    //We could also at an additional tag called "extras". But I don't like that style... So here we go...
    serializeCollection( object.getExtras(), Extra.class, "extra", serializeTo, formatVersion );

    //The statement above does exactly the same as this loop:
    //    for ( Extra extra : object.getExtras() ) {
    //      serialize( Extra.class,  serializeTo.addElement( serializeTo.getNamespace(), "extra" ), extra );
    //    }
  }

Lists can be added easily. If you prefer you might add an additional tag like "extras".

Serialized XML

The serialized XML looks like that:

<?xml version="1.0" encoding="UTF-8"?>
<car xmlns="http://thecompany.com/test/car/1.0.0">
  <color red="255" blue="0" green="200" />
  <model>Ford</model>
  <basePrice>1900000</basePrice>
  <extra>
    <description>Whoo effect</description>
    <price>9998</price>
  </extra>
  <extra>
    <description>Better Whoo effect</description>
    <price>19900</price>
  </extra>
</car>

Deserialization of a Car

Deserialization is always very easy. Just deserialize the stuff using the serializers as delegate. Collections can easily be deserialized using the method deserializeCollection that is defined in AbstractStaxBasedSerializer.

  @Override
  public Car deserialize( XMLStreamReader deserializeFrom, Version formatVersion ) throws IOException, XMLStreamException {
    assert isVersionReadable( formatVersion );
    //We deserialize the color. This should be done in its own serializer in real world --> improved reusability and testability
    nextTag( deserializeFrom, "color" );
    int red = Integer.parseInt( deserializeFrom.getAttributeValue( null, "red" ) );
    int blue = Integer.parseInt( deserializeFrom.getAttributeValue( null, "blue" ) );
    int green = Integer.parseInt( deserializeFrom.getAttributeValue( null, "green" ) );
    Color color = new Color( red, green, blue );
    closeTag( deserializeFrom );

    nextTag( deserializeFrom, "model" );
    Model model = deserialize( Model.class, formatVersion, deserializeFrom );

    nextTag( deserializeFrom, "basePrice" );
    Money basePrice = deserialize( Money.class, formatVersion, deserializeFrom );

    //Now we visit all remaining children (should only be extras)
    List<? extends Extra> extras = deserializeCollection( deserializeFrom, Extra.class, formatVersion );

    return new Car( model, color, basePrice, extras );
  }

Finally

We have created serializers for a quite complex object. Those serializers all just contain very few lines and nearly no boiler plate code.

They can easily be reused and contain enough format version information to be supported in an evolving application without any hassle.

Evolution

If you are interested in how changes to the format can be handled. Take a look at com.cedarsoft.test.io2.MoneySerializer. That serializer supports two different formats.