Skip to content

galan/verjson

Repository files navigation

Verjson is a specialized Java/JSON serialization-library that allows evolvable data-structures on already serialized object-graphs by using transformation and validation steps between versions.

Verjson

Problem description

When you have to send an object-graph from one process to another, you have to serialize the data into an exchange-format that can be read by the receiver. There are plenty of possiblities in the Java space to perform such a task, eg. native Java serialization, XML (XStream, ..) , JSON (Gson, Jackson, ..), SOAP, and more (Avro, Protobuf, ..). These solutions work great until the object-graph changes too much. Some of the libraries also offer version-support (1, ..), most have limitations.

But what if complex real-world changes happen, and you want to keep your object-model code clean? If you want to remove or rename fields, create a Collection from a previously comma-seperated String, change a field to a another type, or other changes?

Of course you can simply deploy the next version of your software/models. But what about the in-flight messages that still have to be processed and are are serialized in some older version? How about long-term storage of such object-graphs if they are written to files, and are a couple of versions behind?

Solution

With Verjson each serialized object-graph includes its version-number along other meta-data. Verjson utilizes Jackson to generate JSON as intermediate-format. When the structure of the object-graph changes, a simple Transformation class has to be implemented that performs the steps to transform from one version to another. The transformation is applied directly on the serialized JSON, which has some benefits such as performance, memory-footprint, avoid code-redundancy, etc.. To ensure correctness the transformed output can be optional validated against an JSON-Schema.

Let's take a quick look on a short example to illustrate the process (source code).

We have a simple bean that should be serialized:

public class ExampleBean {
	String text;
	Long number;
}

First we create a Verjson instance that contains the configuration and manages the serialization. In this case there is no configuration, since this is the first version which requires no transformations (validation is optional, see other examples):

Verjson<ExampleBean> verjson = Verjson.create(ExampleBean.class, null);

Now we can serialize/deserialize ExampleBean objects:

ExampleBean bean = new ExampleBean();
bean.text = "Hello";
bean.number = 42L;

String serializedBean = verjson.write(bean);
ExampleBean deserializedBean = verjson.read(serializedBean);

The requirements change and we want to have a list instead of a single String field. We don't want to loose data and have the content of the "text" field as first element. Furthermore the name of the "number" field should be changed to "counter". We change the ExampleBean (there is no requirement to keep the old version copied to somewhere), now it looks like this:

public class ExampleBean {
	List<String> texts;
	Long counter;
}

In order to transform old data we have to write a Transformation class (Transformations contains static methods that should be static imported):

public class Transformation1 extends Transformation {
	@Override
	protected void transform(JsonNode node) {
		obj(node).put("texts", createArray(remove(obj(node), "text")));
		rename(obj(node), "number", "counter");
	}
}

Then we create the configuration for Verjson by subclassing Versions. This configuration class will grow over time, new versions/transformations, validation will be added. Custom serializer/deserializer and polymorph types will be registered here too.

public class ExampleBeanVersions extends Versions {
	@Override
	public void configure() {
		add(1L, new Transformation1());
	}
}

Here is the modified example that serializes ExampleBean objects in version 2, and also could read serialized objects from version 1 and 2:

Verjson<ExampleBean> verjson = Verjson.create(ExampleBean.class, new ExampleBeanVersions());

ExampleBean bean = new ExampleBean();
bean.texts = Lists.newArrayList("Hello");
bean.counter = 42L;

String serializedBean = verjson.write(bean);
ExampleBean deserializedBean = verjson.read(serializedBean);

Recap

  1. Use Verjson to serialize/deserialize your models
  2. Use an extended Versions object to configure Verjson
  3. Extend a Transformation for each version of your model, do not change that later (create a new version instead)
  4. Create a Validator for each version of your model that you whish to validate against a json-schema

Tips

  • Move your model, Versions, Transformations and json-schemas into an own maven-artifact. So you can manage the versions much easier
  • Unit-test your transformations and json-schemas
  • Take a look into the examples

Possible use-cases:

  • messaging (eg. in-flight messages)
  • long-term storage
  • producer/consumer scenarios
  • data-processing in general
  • ... (tell me how you use it)

Features

  • Serializes/Deserializes into JSON (Jackson based)
  • Custom transformation between versions
  • Support for polymorph types
  • Support for custom-type Serializer/Deserializer
  • Each version can be validated using JSON-Schema
  • Versions can be omitted
  • One line per serialized object (appendable)
  • Thread-safe

Integration with Maven

Just add the following dependency:

<dependency>
	<groupId>de.galan</groupId>
	<artifactId>verjson</artifactId>
	<version>0.7.0</version>
</dependency>