JSON-B is coming
Java EE & JSON
For years, I have been working with Java EE in combination with REST services. Vast majority of REST services today use JSON as data format. However, there is no real standard for binding java objects to JSON and reverse. Gson and Jackson have established as a kind of de facto standard. Nevertheless, I hardly remember any project not facing issues regarding JSON conversion (Just think about date and time adapters).
Java EE7 introduced JSON-P, which is supposed to standardize processing and parsing of JSON. However, this approach uses the builder pattern to create JSON programmatically and does not support binding to POJOs. In Q3 of this year, EE8 now brings ‘Java API for JSON Binding’ (JSON-B) to close that gap. Time for a closer look.
Getting started
Using maven we need to add at least three dependencies to our pom.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<dependency> <groupId>javax.json.bind</groupId> <artifactId>javax.json.bind-api</artifactId> <version>1.0.0-M1</version> </dependency> <dependency> <groupId>org.eclipse</groupId> <artifactId>yasson</artifactId> <version>1.0.0-M1</version> </dependency> <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.json</artifactId> <version>1.1.0-SNAPSHOT</version> </dependency> |
Artifact javax.json.bind-api provides the standard API, yasson provides the reference implementation. As you can see, there are no finales released yet. Therefore, you may need to add the project’s snapshot repositories as shown in listing 2.
1 2 3 4 5 6 7 8 9 10 11 12 |
<repositories> <repository> <id>java.net-Public</id> <name>Maven Java Net Snapshots and Releases</name> <url>https://maven.java.net/content/groups/public/</url> </repository> <repository> <id>yasson-releases</id> <name>Yasson Release repository</name> <url>https://repo.eclipse.org/content/repositories/yasson-releases/</url> </repository> </repositories> |
A first example
Now, we are ready to write our converter:
1 2 3 4 5 6 7 |
public class JsonConverter { public String object2json(Object entity) { Jsonb jsonb = JsonbBuilder.create(); return jsonb.toJson(entity); } } |
Looks easy and reminds me of Gson. JsonbBuilder has two tasks: First, it encapsulates access to provider specific implementation (in this case Yasson). Second, it creates an object of type Jsonb that will be used for JSON binding operations.
Having that, we start binding a simple entity with some primitive data types. Listing 4 shows a first unit test.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/** * Use public fields for simplicity. */ public class Entity { public String stringAttribute; public int intAttribute; public boolean booleanAttribute; } public class JsonConverterTest { private JsonConverter converter = new JsonConverter(); @Test public void object2json_primatives() { Entity myEntity = new Entity(); myEntity.stringAttribute = "sample String value"; myEntity.intAttribute = 4711; myEntity.booleanAttribute = true; String jsonResult = converter.object2json(myEntity); String expectedJson = "{" // + "\"booleanAttribute\":true," // + "\"intAttribute\":4711," // + "\"stringAttribute\":\"sample String value\"" // + "}"; assertEquals(expectedJson, jsonResult); } } |
Binding works! Using standard behaviour our simple class Entity does not need any annotations.
Collections, transient fields & co.
Of course, we can also handle typical use-cases including transient fields, collections or custom data types. Listing 5 extends our Entity class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class Entity { // ... public List<String> list; @JsonbTransient public String ignoredAttribute; @JsonbTypeAdapter(SubEntityJsonbAdapter.class) public SubEntity subEntity; } public class SubEntity { public String name; public SubEntity(String name) { this.name = name; } } |
As you can see, lists are supported out of the box and do not need any mapping metadata. In contrast, to tell JSON-B to ignore field ignoredAttribute when serializing an object we need to add annotation JsonbTransient.
Complex attributes (such as class SubEntity) are mapped recursively by default. However, if you want to setup a custom mapping, add annotation JsonbTypeAdapter naming the JsonbAdapter to be used for binding. A JsonbAdapter defines a mapping from any custom data type to a primitive data type. Listing 6 shows a basic implementation.
1 2 3 4 5 6 7 8 9 10 11 12 |
public class SubEntityJsonbAdapter implements JsonbAdapter<SubEntity, String> { @Override public String adaptToJson(SubEntity subEntity) throws Exception { return subEntity.name; } @Override public SubEntity adaptFromJson(String jsonValue) throws Exception { return new SubEntity(jsonValue); } } |
Date and time out of the box
In projects I have spent hours for discussions and bugs regarding date and time conversion. JSON-B brings support for Java 8 date and time API out of the box. Listing 7 shows how to easily bind date or time values.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class Entity { // ... public LocalDateTime dateTimeAttribute; public LocalDate dateAttribute; } @Test public void object2json_date() { Entity myEntity = new Entity(); myEntity.dateTimeAttribute = LocalDateTime.of(2017, Month.FEBRUARY, 15, 20, 30, 40); myEntity.dateAttribute = LocalDate.of(2017, Month.FEBRUARY, 20); String jsonResult = converter.object2json(myEntity); String expectedJson = "{" // + "\"booleanAttribute\":false," // + "\"dateAttribute\":\"2017-02-20\"," // + "\"dateTimeAttribute\":\"2017-02-15T20:30:40\"," // + "\"intAttribute\":0" // + "}"; assertEquals(expectedJson, jsonResult); } |
JSON-B automatically supports dates and times without any self-written type adapters. By default, a valid ISO 8601 date format will be applied. If you want to change date format or locale, just use annotation JsonbDateFormat.
JSON to Pojo
Until now, we just looked at the conversion from Pojos to JSON. Of course, JSON-B supports the opposite direction. Listing 8 extends our JsonConverter.
1 2 3 4 5 6 7 8 9 |
public class JsonConverter { // ... public <T> T json2object(String json, Class<T> entityType) { Jsonb jsonb = JsonbBuilder.create(); return jsonb.fromJson(json, entityType); } } |
The code looks similar to Listing 2. Again, we create an instance of Jsonb. To bind a JSON string we need to provide the target class’ type. The unit test in listing 9 shows our converter in action.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Test public void json2object() { String json = "{" // + "\"booleanAttribute\":true," // + "\"list\":[\"foo\",\"bar\"]," // + "\"dateAttribute\":\"2017-02-20\"," // + "\"dateTimeAttribute\":\"2017-02-15T20:30:40\"," // + "\"intAttribute\":4711," // + "\"stringAttribute\":\"sample String value\"," // + "\"subEntity\":\"Tom\"" // + "}"; Entity result = converter.json2object(json, Entity.class); assertEquals("sample String value", result.stringAttribute); assertEquals(4711, result.intAttribute); assertEquals(true, result.booleanAttribute); assertEquals(Arrays.asList("foo", "bar"), result.list); assertEquals(LocalDateTime.of(2017, Month.FEBRUARY, 15, 20, 30, 40), result.dateTimeAttribute); assertEquals(LocalDate.of(2017, Month.FEBRUARY, 20), result.dateAttribute); assertEquals(new SubEntity("Tom"), result.subEntity); } |
Use-case specific binding
For now, we added JsonbXXX annotations to our sample entity. If you want to setup a global standard within your project, you can either apply annotations on package level or configure the JsonbBuilder as shown in listing 10.
1 2 3 |
JsonbConfig jsonbConfig = new JsonbConfig() // .withDateFormat(JsonbDateFormat.TIME_IN_MILLIS, Locale.GERMAN); Jsonb jsonb = JsonbBuilder.create(jsonbConfig); |
Production ready?
Well, no. Looking at the JCP process the Proposed Final Draft is still outstanding. However, the API seems to be feature complete. Having Yasson, there is already a working reference implementation. Yasson just published the first milestone release. Nevertheless, this release still includes a couple of snapshot dependencies. In addition, my hands-on revealed some bugs (or just missing features) using the custom JsonbConfig.
In the end, I am really looking forward to the final JSON-B release. In EE world, JSON-B has the potential to replace Gson or Jackson.
Leave a Comment