blob: a929301fbe293a8343ed60c50d9c465f5ede24bb [file]
:component-name: Json Binding 3.0.1 Users Guide
:toc: left
:toclevels: 3
:source-highlighter: coderay
= {component-name}
JSON-B is a standard binding layer for converting Java objects to/from JSON messages. It defines a default mapping algorithm for converting existing Java classes to JSON, while enabling developers to customize the mapping process through the use of Java annotations.
== Installation
=== Coordinates
[source,xml]
.Maven Dependencies
----
<dependencies>
<!-- JSON-P API -->
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.1.3</version>
</dependency>
<!-- Eclipse Parsson (JSON-P implementation) -->
<dependency>
<groupId>org.eclipse.parsson</groupId>
<artifactId>parsson</artifactId>
<version>1.1.6</version>
<scope>runtime</scope>
</dependency>
<!-- JSON-B API -->
<dependency>
<groupId>jakarta.json.bind</groupId>
<artifactId>jakarta.json.bind-api</artifactId>
<version>3.0.1</version>
</dependency>
<!-- Yasson (JSON-B implementation) -->
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>yasson</artifactId>
<version>3.0.4</version>
<scope>runtime</scope>
</dependency>
</dependencies>
----
== JSON Binding API
=== Default mapping
Default mapping is a set of rules used by JSON-B engine be default without any customization annotations and custom configuration provided.
The main entry point in JSON-B is the Jsonb class. It provides a set of overloaded toJson and fromJson methods to serialize Java objects to JSON documents and deserialize them back. Jsonb instances are thread safe and can be reused. The recommended way is to have a single instance per configuration type.
Default Jsonb engine can be created like this:
[source,java]
----
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
// ...
Jsonb jsonb = JsonbBuilder.create();
----
==== Mapping an object
The sample below demonstrates serialization and deserialization of a simple POJO.
[source,java]
----
public static class Dog {
public String name;
public int age;
public boolean bites;
}
// Create a dog instance
Dog dog = new Dog();
dog.name = "Falco";
dog.age = 4;
dog.bites = false;
// Create Jsonb and serialize
Jsonb jsonb = JsonbBuilder.create();
String result = jsonb.toJson(dog);
// Deserialize back
dog = jsonb.fromJson("{\"name\":\"Falco\",\"age\":4,\"bites\":false}", Dog.class);
----
==== Mapping a collection
JSON-B supports raw collections serialization.
[source,java]
----
// List of dogs
List dogs = new ArrayList();
dogs.add(falco);
dogs.add(cassidy);
// Create Jsonb and serialize
Jsonb jsonb = JsonbBuilder.create();
String result = jsonb.toJson(dogs);
// We can also deserialize back into a raw collection, but since there is no way to infer a type here,
// the result will be a list of java.util.Map instances with string keys.
dogs = jsonb.fromJson(result, ArrayList.class);
----
==== Mapping a generic collection
JSON-B supports collections and generic collections handling. For proper deserialization the runtime type of resulting object needs to be passed to JSON-B during deserialization. It can be done a way shown below.
[source,java]
----
// List of dogs
List<Dog> dogs = new ArrayList<>();
dogs.add(falco);
dogs.add(cassidy);
// Create Jsonb and serialize
Jsonb jsonb = JsonbBuilder.create();
String result = jsonb.toJson(dogs);
// Deserialize back
dogs = jsonb.fromJson(result, new ArrayList<Dog>(){}.getClass().getGenericSuperclass());
----
=== Customized mapping
Your mappings can be customized in many different ways. You can use JSON-B annotations for compile time customizations and JsonbConfig class for runtime customizations.
The sample below shows how to create JSON-B engine with custom configuration:
[source,java]
----
// Create custom configuration
JsonbConfig config = new JsonbConfig();
// Create Jsonb with custom configuration
Jsonb jsonb = JsonbBuilder.create(config);
----
==== Formatted output
[source,java]
----
// Create custom configuration with formatted output
JsonbConfig config = new JsonbConfig()
.withFormatting(true);
// Create Jsonb with custom configuration
Jsonb jsonb = JsonbBuilder.create(config);
// Use it!
String result = jsonb.toJson(pojo);
----
==== Changing property names
By default, JSON property name is the same as Java property name. A common use case is serializing properties using different names. This can be achieved using @JsonbProperty annotation on fields or globally using JsonbNamingStrategy.
===== @JsonbProperty annotation
@JsonbProperty is used to change name of one particular property. It can be placed
- on field, in this case it will affect serialization and deserialization
- on getter, in this case it will affect serialization only
- on setter, in this case it will affect deserialization only
In the sample below property property name will be serialized as 'person-name'.
[source,java]
----
public class Person {
@JsonbProperty("person-name")
public String name;
public String profession;
}
----
The resulting JSON document will look like this:
[source,json]
----
{
"person-name": "Jason Bourne",
"profession": "Super Agent"
}
----
The same JSON document will be produced if @JsonbProperty annotation is placed on getter like this:
[source,java]
----
public class Person {
private String name;
private String profession;
@JsonbProperty("person-name")
public String getName() {
return name;
}
public String getProfession() {
return profession;
}
// public setters ...
}
----
This sample demonstrating an ability to write property to one JSON-property and read from another. Property 'name' is serialized to 'name-to-write' property and read from 'name-to-read' property during deserialization.
[source,java]
----
public class Person {
private String name;
private String profession;
@JsonbProperty("name-to-write")
public String getName() {
return name;
}
@JsonbProperty("name-to-read")
public void setName(String name) {
this.name = name;
}
// other public getters/setters ...
}
----
===== Naming Strategies
Naming strategy is used to change a default way of constructing property names.
Supported naming strategies are:
- IDENTITY (myMixedCaseProperty)
- LOWER_CASE_WITH_DASHES (my-mixed-case-property)
- LOWER_CASE_WITH_UNDERSCORES (my_mixed_case_property)
- UPPER_CAMEL_CASE (MyMixedCaseProperty)
- UPPER_CAMEL_CASE_WITH_SPACES (My Mixed Case Property)
- CASE_INSENSITIVE (mYmIxEdCaSePrOpErTy)
- Or your custom implementation of JsonbNamingStrategy interface
IDENTITY strategy is the default one.
It can be applied using with withPropertyNamingStrategy method of JsonbConfig class:
[source,java]
----
// Custom configuration
JsonbConfig config = new JsonbConfig()
.withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_DASHES);
// Create Jsonb with custom configuration
Jsonb jsonb = JsonbBuilder.create(config);
...
----
==== Properties order
To customize the order of serialized properties, JSON Binding provides PropertyOrderStrategy class.
The supported strategies are:
- LEXICOGRAPHICAL (A-Z)
- ANY (order is undefined, in the most cases it will an order in which properties appear in the class)
- REVERSE (Z-A)
Default order strategy is LEXICOGRAPHICAL.
Order strategy can be applied globally using withPropertyOrderStrategy method of JsonbConfig class:
[source,java]
----
// Custom configuration
JsonbConfig config = new JsonbConfig()
.withPropertyOrderStrategy(PropertyOrderStrategy.ANY);
// Create Jsonb with custom configuration
Jsonb jsonb = JsonbBuilder.create(config);
...
----
or using @JsonbPropertyOrder annotation on class:
[source,java]
----
@JsonbPropertyOrder(PropertyOrderStrategy.ANY)
public class Person {
private String name;
private String profession;
// public getters/setters ...
}
----
==== Ignoring properties
By default, JSONB ignores properties with a non public access. All public properties - either public fields or
non public fields with public getters are serialized into JSON text.
Excluding properties can be done with a @JsonbTransient annotation. Class properties annotated with
@JsonbTransient annotation are ignored by JSON Binding engine. The behavior is different depending on
where @JsonbTransient annotation is placed.
- On field: Property is ignored during serialization and deserialization.
- On getter: Property is ignored during serialization only.
- On setter: Property is ignored during deserialization only.
Serialization of this class
[source,java]
----
@JsonbPropertyOrder(PropertyOrderStrategy.ANY)
public class Person {
@JsonbTransient
private String name;
private String profession;
// public getters/setters ...
}
----
will produce the following JSON document:
[source,json]
----
{
"profession": "Super Agent"
}
----
If @JsonbTransient annotation is placed on getter like this:
[source,java]
----
public class Person {
private String name;
private String profession;
@JsonbTransient
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// other public getters/setters ...
}
----
'name' property won't be serialized, but will be deserialized.
==== Null handling
By default JSON-B doesn't serialize fields with null values. This may be a not desired behaviour.
There are three different ways to change default null handling.
- On a class or package level using @JsonbNillable annotation
[source,java]
----
@JsonbNillable
public class Person {
private String name;
private String profession;
// public getters/setters ...
}
----
- On individual properties using @JsonbProperty annotation with nillable=true parameter
[source,java]
----
public class Person {
@JsonbProperty(nillable=true)
private String name;
private String profession;
// public getters/setters ...
}
----
- Globally using withNullValues method of JsonbConfig class
[source,java]
----
// Create custom configuration
JsonbConfig nillableConfig = new JsonbConfig()
.withNullValues(true);
// Create Jsonb with custom configuration
Jsonb jsonb = JsonbBuilder.create(nillableConfig);
...
----
==== Custom instantiation
By default, public default no-arguments constructor is required for deserialization. In many scenarios this requirement is too strict. JSON-B provides @JsonbCreator annotation which can be used to annotate a custom constructor with parameters or a static factory method used to create a class instance.
The sample below shows how @JsonbCreator annotation can be used on a custom constructor. @JsonbProperty annotation on the constructor parameter is required for proper parameter substitution. In this case a value of JSON property 'name' will be passed to the constructor.
[source,java]
----
public class Person {
private String name;
private String profession;
@JsonbCreator
public Person(@JsonbProperty("name") String name) {
this.name = name;
}
// public getters/setters ...
}
----
==== Date/Number Formats
By default JSON-B uses ISO formats to serialize and deserialize date and number fields. Sometimes it's required to override these settings. It can be done using @JsonbDateFormat and @JsonbNumberFormat annotations on fields:
[source,java]
----
public class Person {
private String name;
@JsonbDateFormat("dd.MM.yyyy")
private LocalDate birthDate;
@JsonbNumberFormat("#0.00")
private BigDecimal salary;
// public getters/setters ...
}
----
or globally using withDateFormat method of JsonbConfig class:
[source,java]
----
// Create custom configuration
JsonbConfig config = new JsonbConfig()
.withDateFormat("dd.MM.yyyy", null);
// Create Jsonb with custom configuration
Jsonb jsonb = JsonbBuilder.create(config);
...
----
==== Binary Encoding
JSON-B supports mapping of binary data. By default it uses BYTE encoding, but it can be easily customized using BinaryDataStrategy class which provides support for the most common binary data encodings:
- BYTE
- BASE_64
- BASE_64_URL
The sample below shows a creation of Jsonb engine with BASE_64_URL encoding:
[source,java]
----
// Create custom configuration
JsonbConfig config = new JsonbConfig()
.withBinaryDataStrategy(BinaryDataStrategy.BASE_64);
// Create Jsonb with custom configuration
Jsonb jsonb = JsonbBuilder.create(config);
...
----
==== Adapters
In some cases it may be difficult to serialize/deserialize a class the desired way. Sometimes it's not possible to put custom annotations on the source code because you don't have access to it or custom annotations don't solve the problem. In this case adapters is another option to try.
Adapter is a class implementing jakarta.json.bind.adapter.JsonbAdapter interface. It has a custom code to convert the “unmappable” type (Original) into another one (Adapted) that is serialized/deserialized the desired way. It's similar to how type adapters in JAXB work.
Let's take a look at the sample. Imagine that you have a Customer object with all customer details. In one scenario you need to serialize the whole object, in another you need to provide only its id and name. The solution could be to to serialize it as it is for the first scenario and create an adapter to JsonObject which has only data required for the second scenario.
[source,java]
----
public class Customer {
private int id;
private String name;
private String organization;
private String position;
// public getters/setters ...
}
public class CustomerAnnotated {
@JsonbProperty("customer_id")
private int id;
@JsonbProperty("customer_name")
private String name;
// public getters/setters ...
}
public class CustomerAdapter implements JsonbAdapter<Customer, CustomerAnnotated> {
@Override
public CustomerAnnotated adaptToJson(Customer c) throws Exception {
if (c == null)
return null;
CustomerAnnotated customer = new CustomerAnnotated();
customer.setId(c.getId());
customer.setName(c.getName());
return customer;
}
@Override
public Customer adaptFromJson(CustomerAnnotated adapted) throws Exception {
if (adapted == null)
return null;
Customer customer = new Customer();
customer.setId(adapted.getId());
customer.setName(adapted.getName());
return customer;
}
}
----
First scenario:
[source,java]
----
// Create Jsonb with default configuration
Jsonb jsonb = JsonbBuilder.create();
// Create customer
Customer customer = new Customer();
// Initialization code is skipped
// Serialize
jsonb.toJson(customer);
----
Result:
[source,json]
----
{
"id": 1,
"name": "Jason Bourne",
"organization": "Super Agents",
"position": "Super Agent"
}
----
Second scenario:
[source,java]
----
// Create custom configuration
JsonbConfig config = new JsonbConfig()
.withAdapters(new CustomerAdapter());
// Create Jsonb with custom configuration
Jsonb jsonb = JsonbBuilder.create(config);
// Create customer
Customer customer = new Customer();
// Initialization code is skipped
// Serialize
jsonb.toJson(customer);
----
Result:
[source,json]
----
{
"customer_id": 1,
"customer_name": "Jason Bourne",
}
----
==== Serializers/Deserializers
Sometimes adapters mechanism is not enough and low level access to JSONP parser/generator is needed.
From the spec:
Serializer is a class implementing jakarta.json.bind.serializers.JsonbSerializer interface. It is used to serialize the type it’s registered on (Original). On serializing of Original type JSONB calls JsonbSerializer::serialize method. This method has to contain a custom code to serialize Original type using provided JsonpGenerator.
Deserializer is a class implementing jakarta.json.bind.serializers.JsonbDeserializer interface. It is used to deserialize the type it’s registered on (Original). On deserialization of Original type JSONB calls JsonbDeserializer::deserialize method. This method has to contain a custom code to deserialize Original type using provided JsonpParser.
Let's take a look at the sample. Imagine that we would like to serialize and deserialize a list of pet carriers. A carried pet defined by an abstract class Animal. It can be any of its subclasses. We would like to properly serialize and deserialize it. In order to do it we need to preserve a type information in JSON document and use it on deserialization. It can be done using custom serializer/deserializer pair.
Model:
[source,java]
----
public static class Customer {
private int id;
private String name;
private String organization;
private String position;
// public getters/setters
}
----
Serializer/Deserializer:
[source,java]
----
public static class CustomerSerializer implements JsonbSerializer<Customer> {
@Override
public void serialize(Customer customer, JsonGenerator generator, SerializationContext ctx) {
generator.writeStartObject();
generator.write("customer_id", customer.getId());
generator.write("customer_name", customer.getName());
generator.writeEnd();
}
}
public static class CustomerDeserializer implements JsonbDeserializer<Customer> {
@Override
public Customer deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
Customer customer = new Customer();
JsonParser.Event next;
// Moving parser by hand looking for customer_id and customer_name properties
while ((next = parser.next()) != JsonParser.Event.END_OBJECT) {
if (next == JsonParser.Event.KEY_NAME) {
String jsonKeyName = parser.getString();
// Move to json value
parser.next();
if ("customer_id".equals(jsonKeyName)) {
customer.setId(parser.getInt());
} else if ("customer_name".equals(jsonKeyName)) {
customer.setName(parser.getString());
}
}
}
return customer;
}
}
----
Usage:
[source,java]
----
// Create pojo
Customer customer = new Customer();
customer.setId(1);
customer.setName("Freddie");
// Also configurable with @JsonbSerializer / JsonbDeserializer on properties and class.
JsonbConfig config = new JsonbConfig()
.withSerializers(new CustomerSerializer())
.withDeserializers(new CustomerDeserializer());
Jsonb jsonb = JsonbBuilder.create(config);
String json = jsonb.toJson(customer);
Customer result = jsonb.fromJson(json, Customer.class);
----
==== Strict I-JSON support
https://tools.ietf.org/html/draft-ietf-json-i-json-06[I-JSON] (”Internet JSON”) is a restricted profile of JSON. JSON-B fully supports I-JSON by default with three exceptions:
- JSON Binding does not restrict the serialization of top-level JSON texts that are neither objects nor arrays. The restriction should happen at application level.
- JSON Binding does not serialize binary data with base64url encoding.
- JSON Binding does not enforce additional restrictions on dates/times/duration.
Full support mode can be switched on like it's shown below:
[source,java]
----
// Create custom configuration
JsonbConfig config = new JsonbConfig()
.withStrictIJSON(true);
// Create Jsonb with custom configuration
Jsonb jsonb = JsonbBuilder.create(config);
...
----
== Further Information
- JSON-B official web site: https://jakartaee.github.io/jsonb-api
- Jakarta JSON Binding: https://projects.eclipse.org/projects/ee4j.jsonb
- Mailing list: jsonb-dev@eclipse.org
- Yasson (Compatible Implementation): https://github.com/eclipse-ee4j/yasson