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

Maven Dependencies
<dependencies>
    <!-- JSON-P API -->
    <dependency>
        <groupId>javax.json</groupId>
        <artifactId>javax.json-api</artifactId>
        <version>1.1</version>
    </dependency>

    <!-- JSON-P RI -->
    <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>javax.json</artifactId>
        <version>1.1</version>
        <scope>runtime</scope>
    </dependency>

    <!-- JSON-B API -->
    <dependency>
        <groupId>javax.json.bind</groupId>
        <artifactId>javax.json.bind-api</artifactId>
        <version>1.0.0-RC2</version>
    </dependency>

    <!-- Yasson (JSON-B RI) -->
    <dependency>
        <groupId>org.eclipse</groupId>
        <artifactId>yasson</artifactId>
        <version>1.0.0-RC1</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:

Jsonb jsonb = JsonbBuilder.create();

Mapping an object

The sample below demonstrates serialization and deserialization of a simple POJO.

public class Dog {
    public String name;
    public int age;
    public boolean bitable;
}

// Create a dog instance
Dog dog = new Dog();
dog.name = "Falco";
dog.age = 4;
dog.bitable = false;

// Create Jsonb and serialize
Jsonb jsonb = JsonbBuilder.create();
String result = jsonb.toJson(dog);

// Deserialize back
dog = jsonb.fromJson("{name:\"Falco\",age:4,bitable:false}", Dog.class);

Mapping a 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.

// 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);

// Deserialize back
dogs = jsonb.fromJson(result, ArrayList.getClass());

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.

// 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:

// Create custom configuration
JsonbConfig config = new JsonbConfig();

// Create Jsonb with custom configuration
Jsonb jsonb = JsonbBuilder.create(config);

Formatted output

// 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'.

public class Person {
    @JsonbProperty("person-name")
    private String name;

    private String profession;
}

The resulting JSON document will look like this:

{
    "person-name": "Jason Bourne",
    "profession": "Super Agent"
}

The same JSON document will be produced if @JsonbProperty annotation is placed on getter like this:

public class Person {
    private String name;
    private String profession;

    @JsonbProperty("person-name")
    public String getName() {
        return name;
    }
}

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.

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;
    }
}
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:

// 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:

// 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:

@JsonbPropertyOrder(PropertyOrderStrategy.ANY)
public class Person {
    private String name;
    private String profession;
}

Ignoring properties

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

@JsonbPropertyOrder(PropertyOrderStrategy.ANY)
public class Person {
    @JsonbTransient
    private String name;

    private String profession;
}

will produce the following JSON document:

{
    "profession": "Super Agent"
}

If @JsonbTransient annotation is placed on getter like this:

public class Person {
    private String name;
    private String profession;

    @JsonbTransient
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

'name' property won’t gbe 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

@JsonbNillable
public class Person {
    private String name;
    private String profession;
}
  • On individual properties using @JsonbProperty annotation with nillable=true parameter

public class Person {
    @JsonbProperty(nillable=true)
    private String name;

    private String profession;
}
  • Globally using withNullValues method of JsonbConfig class

// 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.

public class Person {
    private String name;
    private String profession;

    @JsonbCreator
    public Person(@JsonbProperty("name") String name) {
        this.name = name;
    }
}

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:

public class Person {
    public String name;

    @JsonbDateFormat("dd.MM.yyyy")
    private Date birthDate;

    @JsonbNumberFormat(#0.00")
    public BigDecimal salary;
}

or globally using withDateFormat method of JsonbConfig class:

// 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:

// 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 javax.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 sililar 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.

public class Customer {
    private int id;
    private String name;
    private String organization;
    private String position;

    ...
}

public class CustomerAdapter implements JsonbAdapter<Customer, JsonObject> {
    @Override
    public JsonObject adaptToJson(Customer c) throws Exception {
        return Json.createObjectBuilder()
            .add("id", c.getId())
            .add("name", c.getName())
            .build();
    }

    @Override
    public Customer adaptFromJson(JsonObject adapted) throws Exception {
        Customer c = new Customer();
        c.setId(adapted.getInt("id"));
        c.setName(adapted.getString("name"));
        return c;
    }
}

First scenario:

// Create Jsonb with default configuration
Jsonb jsonb = JsonbBuilder.create();

// Create customer
Customer c = new Customer();

// Initialization code is skipped

// Serialize
jsonb.toJson(customer);

Result:

{
    "id": 1,
    "name": "Json Bourne",
    "organization": "Super Agents",
    "position": "Super Agent"
}

Second scenario:

// Create custom configuration
JsonbConfig config = new JsonbConfig()
    .withAdapters(new CustomerAdapter());

// Create Jsonb with custom configuration
Jsonb jsonb = JsonbBuilder.create(config);

// Create customer
Customer c = new Customer();

// Initialization code is skipped

// Serialize
jsonb.toJson(customer);

Result:

{
    "id": 1,
    "name": "Json 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 javax.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 javax.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:

public abstract class Animal {
    private String name;
    private int age;
    private Boolean furry;

    ...
}

public class Cat extends Animal {
    private Boolean cuddly;

    ...
}

public class Dog extends Animal {
    private Boolean barking;

    ...
}

public class Carrier<P extends Pet> {
    public enum TYPE {
        BAG, CRATE, TROLLEY
    }

    private TYPE carrierType;
    private P carriedPet;

    @JsonbCreator
    public Carrier(TYPE carrierType, A carriedPet) {
        this.carrierType = carrierType;
        this.carriedPet = carriedPet;
    }

    ...
}

Serializer/Deserializer:

public class AnimalSerializer implements JsonbSerializer<Animal> {
    public void serialize(Animal animal, JsonGenerator jsonGenerator, SerializationContext serializationContext) {
        if (animal != null) {
            serializationContext.serialize(animal.getClass().getName(), animal, jsonGenerator);
        } else {
            serializationContext.serialize(null, jsonGenerator);
        }
    }
}

public class AnimalDeserializer implements JsonbDeserializer<Animal> {
    public Animal deserialize(JsonParser jsonParser, DeserializationContext deserializationContext, Type type) {
        Animal animal = null;
        while (jsonParser.hasNext()) {
            JsonParser.Event event = jsonParser.next();
            if (event == JsonParser.Event.KEY_NAME) {
                String className = jsonParser.getString();
                jsonParser.next();
                try {
                    animal = deserializationContext.deserialize(Class.forName(className).asSubclass(Animal.class), jsonParser);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
        return animal;
    }
}

Usage:

// Create a list of carrier objects
List<Carrier<Pet>> carriers = new ArrayList<>();
carriers.add(new Carrier<>(Carrier.TYPE.BAG, new Cat("Harris", 10, true, true)));
carriers.add(new Carrier<>(Carrier.TYPE.CRATE, new Dog("Falco", 4, false, false)));
Type carrierListType = new ArrayList<Carrier<Pet>>() {}.getClass().getGenericSuperclass();

JsonbConfig config = new JsonbConfig()
        .withFormatting(true)
        .withSerializers(new PetSerializer())
        .withDeserializers(new PetDeserializer());

Jsonb jsonb = JsonbBuilder.create(config);

String json = jsonb.toJson(carriers, carrierListType);
System.out.println(json);

List<Carrier<Pet>> list = jsonb.fromJson(json, carrierListType);
System.out.println(list.get(0).getCarriedPet().getClass().getName());

Strict I-JSON support

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:

// Create custom configuration
JsonbConfig config = new JsonbConfig()
    .withStrictIJSON(true);

// Create Jsonb with custom configuration
Jsonb jsonb = JsonbBuilder.create(config);

...

Further Information