JSON Binding (JSON-B) is the new Java EE specification for converting JSON messages to Java Objects and back. JSON is used everywhere and so far we had two main ways of dealing with JSON conversion in Java- using either Jackson or GSON. With the introduction of JSON-B, we have a standard way of handling this conversion. In this article, we will see how Spring Boot 2.0 supports JSON-B, how easy it is to use it and how does it compare with the other options.
If you want to know more about JSON-B itself, you can visit the official website or read the four parts Getting started with the JSON API by IBM. I found the fourth part comparing JSON-B, Jackson, and GSON very interesting. Now it is time to see JSON-B in action!
Converting Java Objects to JSON with Spring Boot 2.0 and JSON-B
How do you get JSON-B to work with Spring Boot 2.0? It is very simple. You need to add the required Maven dependencies:
<dependency> <groupId>javax.json.bind</groupId> <artifactId>javax.json.bind-api</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.eclipse</groupId> <artifactId>yasson</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.json</artifactId> <version>1.1</version> </dependency>
And you need to choose the preffered-json-mapper
setting to make sure that JSON-B is chosen. You may get GSON or Jackson on the classpath and then you can’t be sure how the autoconfiguration will work without this setting:
spring.http.converters.preferred-json-mapper=jsonb
With that in place, we are going to write a simple Rest Controller and a simple Car Class, that will make use of the JSON-B conversion:
package com.e4developer.jsonbexample; import org.springframework.web.bind.annotation.*; import java.util.Calendar; import java.util.Optional; @RestController public class SimpleController { private Car makeCar() { Car newCar = new Car(); newCar.make = "e4Cars"; newCar.model = "theSensible"; newCar.bonusFeatures = Optional.empty(); newCar.price = 6000; newCar.productionDate = new Calendar.Builder().setDate(2018, 3, 3).build(); return newCar; } @GetMapping("/car") public Car car() { Car newCar = makeCar(); return newCar; } }
package com.e4developer.jsonbexample; import java.util.Calendar; import java.util.Objects; import java.util.Optional; public class Car { public String make; public String model; public int price; public Calendar productionDate; public Optional<String> bonusFeatures; @Override public String toString() { return "Car{" + "make='" + make + '\'' + ", model='" + model + '\'' + ", price=" + price + ", productionDate=" + productionDate + ", bonusFeatures=" + bonusFeatures + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Car car = (Car) o; return price == car.price && Objects.equals(make, car.make) && Objects.equals(model, car.model) && Objects.equals(productionDate, car.productionDate) && Objects.equals(bonusFeatures, car.bonusFeatures); } @Override public int hashCode() { return Objects.hash(make, model, price, productionDate, bonusFeatures); } }
Now let’s see how the response from that endpoint looks like:
{ "make": "e4Cars", "model": "theSensible", "price": 6000, "productionDate": "2018-04-03T00:00:00+01:00[Europe/London]" }
It is worth noting a few things. Treatment of the Calendar
is specific to JSON-B specification and so is the simple disappearing of the missing Optional
. I think the treatment of these two concepts is quite clean here.
The equivalent response with GSON would be:
{ "make": "e4Cars", "model": "theSensible", "price": 6000, "productionDate": { "year": 2018, "month": 3, "dayOfMonth": 3, "hourOfDay": 0, "minute": 0, "second": 0 }, "bonusFeatures": {} }
Note the different treatment of the Calendar
and the quite peculiar thing that happens to Optional
. It is not quite null
, but not quite an Object either.
And with Jackson:
{ "make": "e4Cars", "model": "theSensible", "price": 6000, "productionDate": "2018-04-02T23:00:00.000+0000", "bonusFeatures": null }
Again, the Calendar
is treated differently and the treatment of Optional
at least can be seen as sensible- it is clearly a null
.
If you are interested in a more detailed comparison between JSON-B, GSON, and Jackson standards, I once again recommend the IBM article dealing with the subject.
I showed you these three different examples to really make a point here. There is a lot of value from an official standard that deals with converting Java to JSON. Of course, JavaEE championed standards were not always successful. The original EJBs were mostly a disaster (sorry if you liked them, but sadly they did not catch on). CDI beans were much better (although they did not get enough traction in my opinion). JPA was on the other hand very successful and it is still very popular.
I strongly believe that the JSR 367 (this is Java Specification Requests for JSON-B) is here to stay and will bring a lot of good to the JVM ecosystem. I think we all want a more united and more seamless JSON development experience in the Java world.
We will see now how these different libraries deal with JSON to Java conversion.
Converting JSON to Java Objects
It is important for a converter to be able to convert both ways between Java Object and JSON without changing the object itself. With the new standard, I wanted to see how well it handles this task.
We will use the original Car
Object, get the JSON from JSON-B, GSON, and Jackson and then feed it back with a POST to see how the recreated Object looks like. We will check the equality to the Original Object and inspect any potential changes in the payload when returned once again.
For that I wrote a simple Controller endpoint:
@PostMapping("/sendCar") public Car sendCar(@RequestBody Car car){ System.out.println("original car: "+makeCar()); System.out.println("transformed car: "+car.toString()); System.out.println("Is the car the same? "+ car.equals(makeCar())); return car; }
Conversion with JSON-B
JSON-B passes the simple test of returning the same JSON text that it is sent.
JSON-B fails the equality check! It turns out that JSON-B assigns a naked null
rather than the Optional value to the field when it is not present in the JSON! This is very disappointing, as it may cause unexpected bugs. To be clear what happens is:
- JSON-B deals with
Optional.empty()
by not including it in the JSON response. - When JSON parsed into Java Object does not have a value for an
Optional<>
field it assings itnull
rather thanOptional.empty()
- This breaks the idea of
Optional
This is disappointing enough that I hope it will change in the future iterations of the standard.
Conversion with GSON
GSON passes the simple test of returning the same JSON text that it is sent.
GSON also passes the equality check! It correctly deals with the Optional
and the Calendar
cases.
Conversion with JACKSON
Jackson passes the simple test of returning the same JSON text that it is sent.
Jackson fails the equality check! While Jackson is correctly handling the Optional
case it fails to deal with the Calendar
correctly. It loses the Zone information (in my case London/Europe) and changes it to UTC. This simplification can cause unexpected bugs in different systems.
Conclusion
I believe that JSON-B is a great idea for a new standard. JSON became so important that having Java community agree on a good way of dealing with it can be very helpful.
It is apparent that dealing with JSON to Java and back is not as trivial as it may seem. As demonstrated here, these conversions may end up causing some unexpected side effects. Personally, I would like JSON-B to be more like GSON- perhaps sacrifice human readability for a clear back to back conversion.
No matter which converter you chose, Spring Boot 2.0 makes it easy to use. I am looking forward to JSON-B entering the field as the third worthy contender for your JSON converter of choice.
The code used for this article is available on my Github account.
Hey! Interesting article. 🙂
I thought that `Optional`s weren’t meant to be attributes/parameters but only return values in order to not deal with `null`s. I’d love to know why they are not properly unserialised though…
Hey antoinm!
Even if they are not meant to be attributes parameter by design, they can still be used in that fashion and JSON libraries need some way of dealing with that. Good point though- if you don’t use it, you are not exposed to the risk.
I think the JSON to Java part fails to deal with Optional here, as it is not using a constructor and only manually mapping attributes (not being too smart). With this approach, it seems that Jackson and GSON way of dealing with it is simpler.
Nice examples. A few comments, though:
1) JSON-B is not “the new Java EE standard” — it is simply a specification that is included in Java EE. Java EE is a collection of specifications implemented as a platform. Well, saying that JSON-B is ” the new Java EE standard” is not wrong in itself but in this context (this tutorial is about Spring Boot), the distinction between Java EE and Spring is not clear. I would rephrase it, you can’t imagine how high is the general Java EE/Spring confusion.
Just say that “JSON-B is the new Java API for JSON Binding (JSR-367)” and in the end of the article, if you wish, you can specify: “if you are using JavaEE instead of Spring, JSON-B is supported by default in Java EE 8”.
2) “JSON is used everywhere and so far we had two main ways of dealing with JSON conversion in Java- using either Jackson or GSON.” — this phrase is not entirely correct. JSON-B is a new specification, indeed, for *binding* objects. But we had JSON-P (you know, javax.json.JsonObject) since a long time ago.
3) Why are you including as dependency the following:
org.glassfish
javax.json
1.1
That is the implementation of JSON-P, yet you did not include the JSON-P api. Is it needed somehow for Yasson to work? Does Yasson include the JSON-P api? I am not sure. Either way, you should clarify this JSON-B/JSON-P topic.
Finally, my personal opinion about JSON-B and Yasson is that they are not improving anything. They are simply encouraging get/set POJOs (model objects), which is the worst imaginable thing for OOP. I wrote about it here: http://www.amihaiemil.com/2017/07/04/yasson-yet-another-POJO-parser.html
Thanks for the detailed comment!
1) I edited the sentence to say specification rather than standard. I think it adds clarity.
2) I see where you are coming from. Maybe I could improve the phrasing to be more precise here.
3) The official JSON-B page claims that you need that dependency. I could see if it works without, but I just followed their documentation here. Could be not needed though.
Interesting blog post about POJOs! I personally think POJOs have their place (interacting with some 3rd party APIs), but I agree that anaemic data models are often the wrong way to build your system.
Thanks for the feedback!
Interesting article