Reading/writing Java properties files using Jackson (2.8)
Tags: Java, Json, Android, Properties

I will still need to write more about Jackson 2.7 and 2.8 releases (last one I wrote about was 2.6), but before that let’s talk about one of biggest new things in 2.8: support for reading and writing plain old Java properties using `jackson-dataformat-properties`.

Jackson-supported data formats

Prior to 2.8 Jackson already had support for following data formats with official FasterXML data format modules (in order of addition):

JSON (0.5)

Smile (binary JSON) (1.6)

XML (1.8)

CSV (2.0)

YAML (2.0)

Avro (2.1)

CBOR (binary JSON) (2.4)

Protobuf (2.6)

and there are a few others (see Jackson portal for details) that add support for other formats like BSON, MessagePack and Hocon.

So addition of yet another data format module does not seem like a big deal. Right?

Motivation

As things are, reading and writing Properties from/to files and resources (files stored in jars) is relatively simple. But actual use is cumbersome because access is through untyped (`Properties` is basically a `Map<String,String>`) and flat (no nested values) model: even if underlying information needs to contain typed things (say, URLs, numbers) and at least simple structures like Lists.

For example, you might have very simple configuration Properties file:

title=Home Page

site.host=localhost

site.port=8080

which could approximate contents of a JSON document like:

{ “title” : “Home Page”,

“site” : {

"host” : “localhost”,

“port” : 8080

}

}



and thereby map to a POJO like this:

public class Endpoint {

public String title;

public Site site;

}

public class Site {

public String host;

public int port;

}

but you’d still have to access values using awkward code like:

Properties props = ...;

int port; String portStr = props.get("site.port");

if (portStr == null) {

... // report missing value

}

try {

port = Integer.parseInt(portStr);

} catch (NumberFormatException e) {

.... // handle

}

But this is not how things need to be since:

Implied structure is commonly expressed using de-facto standard of dotted path, which is easily resolvable to intended structure Implied structure matches simple POJOs Leaf values are usually simple scalars (Strings, numbers, booleans)

Because of these properties it is possible to relatively easily map actual contents into POJOs.

This is what `jackson-dataformat-properties` does.

Basic usage

Usage should be familiar to anyone who has used Jackson in general; the main difference is that a subtype of `ObjectMapper`(*) is to be used, but otherwise interface is the same:

JavaPropsMapper mapper = new JavaPropsMapper();

// and then read/write data as usual

Endpoint host = mapper.readValue(new File("site.properties"),

Endpoint.class);

String asText = mapper.writeValueAsString(host);

and there are some additional convenience methods too:

// Or, if you actually require `Properties` object you can use:

Properties props = mapper.writeValueAsProperties(host);

// or, convert a `Properties` to instance to POJO

Endpoint fromProps = mapper.readValue(props, Endpoint.class);

(*) Actually, you can construct plain `ObjectMapper` if you want, with:

ObjectMapper mapper = new ObjectMapper(new JavaPropsFactory());

— just make sure you give it different factory, and not default `JsonFactory`

Advanced Features

But wait! In addition to implied structure of contained POJO types, and simple conversions/coercions, support exists for arrays/Lists as well!

Consider following properties:

boxes.1.x = 5

boxes.1.y = 6

boxes.2.x = -5

boxes.2.y = 15

which could map to, say, a List (or array) of Point objects.

Or, maybe as an alternative:

boxes[1].x = 5

boxes[1].y = 6

boxes[2].x = -5

boxes[2].y = 15

And guess what? This is supported, by default. Just bind to something like:

public class Boxes {

public List<Box> boxes; }

public class Box {

public int x, y;

}

Configurability

Examples above were relying on default settings, but the module allows quite a bit of configurability regarding how individual property values are constructed/deconstructed (modified to read/write).

Configurability is handled by another existing and widely used (with formats like CSV, Protobuf, Avro) interface of `FormatSchema`. For Properties, subtype to use is `JavaPropsSchema`, and you construct instances with a fluent-like methods:

JavaPropsSchema schema = JavaPropsSchema.emptySchema()

// Use brackets instead of dots for array index

.withWriteIndexUsingMarkers(true)

// and start from 3, not 1

.withFirstArrayOffset(3);

String props = propsMapper.writer(schema).writeValueAsString();

and get output like:

boxes[3].x = 5

boxes[3].y = 6

boxes[4].x = -5

boxes[4].y = 15

you may similarly configure other aspects like:

Line ending to use (default: “

”)

Indentation to use, if any

Path separator (default: “.”)

Header (usually comment) to write before serialized property values

Key/value separator to use (default: “=”, but Java properties specification also allows “:”)

Whether “simple” array indexes (numbers as path segments) are recognized or not (default: true; sometimes useful to disable to avoid coercion to array values)

Feedback Appreciated

Since this is a new module, not much feedback has yet been received. Usability and quality usually increase rapidly when users are using the component and reporting both problems, and suggestions for further improvements. So feedback would be appreciated via mailing lists, bug tracker, or Twitter.