Jackson 2.10 jackson-jr improvements

(custom ValueReaders, ValueWriters for 3rd party type support)

Another area of significant improvements in Jackson 2.10 (see earlier “Jackson 2.10 features”) is jackson-jr — light-weight alternative to full jackson-databind (see “jackson-jr for casual JSON reading/writing”).

One of 2 major additions — ability to read “root values” (to support “JSON Streaming”, line-delimited JSON (LDJSON)) — was covered in that blog post. But ability to add read/write support for custom datatypes, such as 3rd party library (such as Joda, Guava) value types, was only briefly mentioned.
Let’s have a look at how you can add custom ValueReaders (to read JSON as specific Java class instances) and ValueWriters (to write Java class instances as JSON).

To write instances of specific type as JSON, you will need to implement interface ValueWriter:

public interface ValueWriter {
public void writeValue(JSONWriter context, JsonGenerator g, Object value) throws IOException;
public Class<?> valueType();
}

and for hypothetical type Point:

public class Point {
public int _x, _y;
public Point(int x, int y) {
_x = x;
_y = y;
}
}

where field names differ from what you want in JSON, you could implement like so

public class PointWriter implements ValueWriter {
public Class<?> valueType() { return Point.class; }
public void writeValue(JSONWriter context, JsonGenerator g,
Point value) throws IOException
{
g.writeStartObject();
g.writeNumberField("x", value._x);
g.writeNumberField("y", value._y);
g.writeEndObject();
}
}

and that’s almost all there is to it — except for telling JSON how to use it.
But before covering that, let’s look at the other part, reading of values

To read JSON as instances specific type, you will need to extend abstract class ValueReader:

public abstract class ValueReader {
public abstract Object read(JSONReader reader, JsonParser p) throws IOException;
// also defines "readNext()" as well as "valueType()"
}

implementation of which could look like this:

public class PointReader extends ValueReader {
public PointReader() { super(Point.class); }
@Override
public Object read(JSONReader reader, JsonParser p) throws IOException {
// Can either use low-level JsonParser or JSONReader:
Map<Object, Object> map = reader.readMap();
// note: if production code, should verify contents
return new Point((Integer) map.get("x"), (Integer) map.get("y"));
}
}

Note: as per comment, implementation can either use standard Jackson JsonParser (low-level streaming parser), or convenience methods of JSONReader: latter is more convenient and allows “delegation”: reading JSON as one of types jackson-jr supports by default (including POJOs), and then converting (but just make sure NOT to try to call with type itself, Point.class in this case — that will lead to recursion and does not work).

With implementations that we have, all there is to do is to create a ReaderWriterProvider that can provide custom reader(s) and/or writer(s) we want. This would look something like:

public class MyHandlerProvider extends ReaderWriterProvider
{
public ValueReader findValueReader(JSONReader readContext,
Class<?> type)
{
if (type == Point.class) {
return new PointReader();
}
return null;
}
public ValueWriter findValueWriter(JSONWriter writeContext,
Class<?> type) {
if (type == Point.class) {
return new PointWriter();
}
return null;
}
}

and then that you would use following configuration to have a JSON instance with support for Point type:

JSON jr = JSON.std.with(new MyHandlerProvider());
String json = jr.asString(new Point(1, 3));
Point p = jr.beanFrom(Point.class, json);

Although jackson-jr handles basic JDK types and Bean types, there are many value types that are unsupported:

  1. Value types with custom constructors
  2. POJOs with non-standard naming convention

so it is good to now have an ability to extend support. But beyond developers adding readers/writers for their own types, it is now also possible to offer shareable extension for 3rd party data types (like Joda, or Guava), as well as extended support for less commonly used JDK types; or to offer configurable alternate handling.

Although the goal is not to create a competitor for “full” jackson-databind — there are trade-offs in keeping things small and fast-to-start — a little bit of additional configurability and extensibility can go a long way. So another area of extensibility that was consider for addition in 2.10 (but did not quite make it due to there being so many good addition ideas to choose from!) is the ability to add something similar to core databind’s Bean[De]SerializerModifier — ability to alter, modify behavior of default ValueReaders and ValueWriters jackson-jr provides.

Issue that tracks this idea and possible plans is jackson-jr#32 and the original inspiration (and possible eventual target) is the ability to add support for some subset of core Jackson annotations; or equivalent configuration mechanism, to supported things like:

  • Ability to rename individual properties (“@JsonProperty”)
  • Ability to ignore individual properties (“@JsonIgnore”)
  • Specific ordering of Bean properties to serialize (“@JsonPropertyOrder”)
  • Possibly support “naming strategies” (to map Java Bean’s “camel case” convention to different convention in encoded JSON)

Open Source developer, most known for Jackson data processor (nee “JSON library”), author of many, many other OSS libraries for Java, from ClassMate to Woodstox