JSON is the current de-facto data format standard for exposing data via APIs. The Java ecosystem offers a bunch of libraries to create JSON from Java objects and vice-versa (GSON, Jackson, etc.). With the release of Java EE 8 and the JSR-367, we now have a standardized approach for this: JSON-B. With the transition of Java EE to the Eclipse Foundation, this specification is now renamed to Jakarta JSON Binding (JSON-B). In addition, this spec is also part of the Eclipse MicroProfile project.
Learn more about the JSON Binding (JSON-B) specification, its annotations, and how to use it in this blog post.
Specification profile: JSON Binding (JSON-B)
- Current version: 1.0
- GitHub repository
- Specification homepage
- Basic use case: Convert Java objects from and to JSON
Map objects from and to JSON
The central use case for JSON-B is mapping Java objects to and from JSON strings. To provide you an example, I'm using the following POJO:
1 2 3 4 5 6 7 8 9 10 11 12 | public class Book { private String title; private LocalDate creationDate; private long pages; private boolean isPublished; private String author; private BigDecimal price; // constructors, getters & setters } |
Mapping Java objects and JSON messages requires an instance of the Jsonb
. The specification defines a builder to create such an object. This instance can then be used for both mapping Java objects from and to JSON:
1 2 3 4 5 6 7 | Book book = new Book("Java 11", LocalDate.now(), 1, false, "Duke", new BigDecimal(44.444)); Jsonb jsonb = JsonbBuilder.create(); String resultJson = jsonb.toJson(book); Book serializedBook = jsonb.fromJson(resultJson, Book.class); |
With no further configuration or adjustments, the JSON result contains all Java member variables (ignoring null values) as attributes in camel case.
Furthermore, you can also map a collection of Java objects to and from JSON arrays in a type-safe manner:
1 2 3 4 5 6 7 8 9 10 | List<Book> bookList = new ArrayList<>(); bookList.add(new Book("Java 11", LocalDate.now(), 100, true, "Duke", new BigDecimal(39.95))); bookList.add(new Book("Java 15", LocalDate.now().plus(365, ChronoUnit.DAYS), 110, false, "Duke", new BigDecimal(50.50))); Jsonb jsonb = JsonbBuilder.create(); String result = jsonb.toJson(bookList); List<Book> serializedBookList = jsonb .fromJson(result, new ArrayList<Book>(){}.getClass().getGenericSuperclass()); |
Configure the mapping of attributes
Sometimes the default mapping strategy of JSON-B might not fit your requirements and you want to e.g. customize the JSON attribute name or the date/number format. The specification offers a set of annotations to override the default mapping behavior, which can be applied to your Java POJO class.
With @JsonbProperty
you can adjust the name of the JSON attribute name. If you use this annotation on a field level, it will affect both serialization and deserialization. On getter methods it affects only serialization and on setters only deserialization back to Java objects:
1 2 | @JsonbProperty("book-title") private String title; |
Next, you can use @JsonbTransient
to avoid the serialization of a specific attribute to JSON at all:
1 2 | @JsonbTransient private boolean isPublished; |
If you plan to override the default behavior to not include null values to the JSON message, @JsonbNillable
offers a way to do this. This annotation can only be used on class level and will affect all attributes:
1 2 3 | @JsonbNillable public class Book { } |
For those use cases where you just want one attribute to be serialized to null, you can use @JsonbProperty(nillable=true)
on fields/getters/setters.
In addition, you are able to adjust the format of dates and numbers with @JsonbDateFormat
and @JsonbNumberFormat
and specify your custom format:
1 2 3 4 5 | @JsonbDateFormat("dd.MM.yyyy") private LocalDate creationDate; @JsonbNumberFormat("#0.00") private BigDecimal price; |
Above all, if you don't want JSON-B to use the default no-arg constructor to deserialize JSON to Java objects, you can specify a custom constructor and use the annotation @JsonbCreator
:
1 2 3 4 5 6 7 8 9 10 | public class Book { // ... @JsonbCreator public Book(@JsonbProperty("book-title") String title) { this.title = title; } } |
Make sure you use this annotation only once per class.
Define metadata for mapping JSON objects
Applying e.g. the @JsonbDateFormat
to all your POJOs so they are all compliant to your custom date format, might be cumbersome and error-prone. Furthermore, if you use the annotations above to customize the mapping, you are not able to provide multiple representations if different clients require their own.
You can solve such requirements with a JsonbConfig
instance and define global metadata for the mapping. Together with this configuration class, you can create a configured Jsonb
instance and apply the mapping rules to all mappings of this instance:
1 2 3 4 5 6 7 8 9 10 11 12 | Book book = new Book("Java 11", LocalDate.now(), 1, false, null, new BigDecimal(50.50)); JsonbConfig config = new JsonbConfig() .withNullValues(false) .withFormatting(true) .withPropertyOrderStrategy(PropertyOrderStrategy.LEXICOGRAPHICAL) .withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_UNDERSCORES) .withDateFormat("dd-MM-YYYY", Locale.GERMAN); Jsonb jsonb = JsonbBuilder.create(config); String jsonString = jsonb.toJson(book); |
Using this JsonbConfig
, you are also able to configure things you can't with the annotations of the previous chapter: pretty-printing, locale information, naming strategies, ordering of attributes, encoding information, binary data strategies, etc. Have a look at the official user guide for all configuration attributes.
Provide a custom JSON-B mapping strategy
If all of the above solutions don't meet your requirements for mapping Java objects to and from JSON, you can implement your own JsonAdpater
and get full access to serialization and deserialization:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class BookAdapter implements JsonbAdapter<Book, JsonObject> { @Override public JsonObject adaptToJson(Book book) throws Exception { return Json.createObjectBuilder() .add("title", book.getTitle() + " - " + book.getAuthor()) .add("creationDate", book.getCreationDate().toEpochDay()) .add("pages", book.getPages()) .add("price", book.getPrice().multiply(BigDecimal.valueOf(2l))) .build(); } @Override public Book adaptFromJson(JsonObject jsonObject) throws Exception { Book book = new Book(); book.setTitle(jsonObject.getString("title").split("-")[0].trim()); book.setAuthor(jsonObject.getString("title").split("-")[1].trim()); book.setPages(jsonObject.getInt("pages")); book.setPublished(false); book.setPrice(BigDecimal.valueOf(jsonObject.getJsonNumber("price").longValue())); book.setCreationDate(LocalDate.ofEpochDay(jsonObject.getInt("creationDate"))); return book; } } |
With this adapter, you have full access to manage your JSON representation and to the deserialization logic. In this example, I'm using both the title and author for the final book title and concatenate both. Keep in mind that with a custom adapter, your JSON-B annotations on your POJO are overruled.
To make use of this JsonAdapter
, you have to register it using a custom JsonbConfig
:
1 2 3 4 5 6 7 8 | JsonbConfig config = new JsonbConfig() .withAdapters(new BookAdapter()); Jsonb jsonb = JsonbBuilder.create(config); String jsonString = jsonb.toJson(book); Book serializedBook = jsonb.fromJson(jsonString, Book.class); |
If you need more low-level access to the serialization and deserialization, have a look at the JsonbSerializer
and JsonbDeserializer
interface (an example can be found in the official user guide).
YouTube video for using JSON-B 1.0
Watch the following YouTube video of my Getting started with Eclipse MicroProfile series to see JSON-B 1.0 in action:
You can find the source code with further instructions to run this example on GitHub. If you are looking for a solution to process JSON data, have a look at JSON-P.
Have fun using JSON-B,
Phil