Contents


Get started with the JSON Binding API, Part 1

The JSON Binding API in a nutshell

Get started with JSON-B's default features and custom annotations, runtime configurations, and more

Comments

Content series:

This content is part # of 4 in the series: Get started with the JSON Binding API, Part 1

Stay tuned for additional content in this series.

This content is part of the series:Get started with the JSON Binding API, Part 1

Stay tuned for additional content in this series.

A headline feature in Java™ EE 8, the Java API for JSON Binding (JSON-B) 1.0 (JSR 367), strengthens Java's support for JavaScript Object Notation, or JSON.

JSON has become the standard for transferring information between connected and disconnected systems. Popular for cloud and web services applications, it has emerged as the go-to data interchange format for RESTful web services in microservices-based systems.

JSON-B completes Java EE’s suite of APIs and features for managing JSON, providing a simple, uniform standard for converting Java objects to JSON documents and back again. Its ease of use is thanks in part to minimal ceremony, and to sensible and intuitive default configurations.

JSON Binding also combines well with JSON Processing, Java EE’s other API for managing JSON in enterprise systems. JSON-P is a lower-level API that provides two models (streaming and object) for JSON processing and transformation. Together, the two APIs offer a solid framework for using JSON in Java enterprise systems.

Default and custom binding with JSON-B

Out of the box, JSON-B provides default mappings for serialization and deserialization that are suitable for everyday programming scenarios. For simpler customizations, the API provides adapters and some custom serializers and deserializers. For more advanced scenarios, it’s possible to use in-class annotations or a runtime configuration builder to override JSON-B’s default settings.

On the whole, JSON-B codifies industry practices and methodologies that have become commonplace for enterprise developers. It uses annotations to mark classes and fields with mapping semantics, and it provides the extensibility that is so often required to deal with complex data structures.

Most importantly, this API provides binding support between Java classes and JSON documents in an intuitive and easy manner. Even developers with no prior experience with JSON should easily pick up and use the Java API for JSON Binding. Developers who are experienced using JSON de/serialization libraries such as GSON, Boon, and Jackson will find JSON-B familiar and comfortable to use.

Two interfaces: Jsonb and JsonBuilder

Functionally speaking, the JSON-B API consists of two entrypoint interfaces: javax.json.bind.Jsonb and javax.json.bind.JsonBuilder. The Jsonb interface enables serialization and deserialization via the methods toJson() and fromJson(), respectively. The JsonbBuilder interface constructs Jsonb instances based on a set of optional configurations and provides client access to the instance.

Roundtrip example

There is no better way to get started with a new API than trying it for yourself. In this example, we’ll build a basic example application using a simple class. In Listing 1, we first serialize the Book class to a JSON document, then deserialize it back to a Book object.

Listing 1. A minimal Book class
public class Book {
   private String id;
   private String title;
   private String author;
   // Plumbing code removed for brevity
}

To serialize a Book object, we first need to create a Jsonb instance, which we do using the JsonbBuilder API’s static create() factory method. Once we have the Jsonb instance, we call one of the overloaded toJson() methods and pass it the object to serialize, as shown in Listing 2.

Listing 2. Serializing a Book instance
Book book = new Book("ABCD-1234", "Fun with Java", "Alex Theedom");
Jsonb jsonb = JsonbBuilder.create();
String json = jsonb.toJson(book);

The toJson() method returns the serialized Book object as shown in Listing 3.

Listing 3. Returning the serialized Book instance
{
  "author": "Alex Theedom",
  "id": "ABCD-1234",
  "title": "Fun with Java"
}

Notice the lexicographic order of the properties in Listing 3. This is the default property order, and is customizable. We’ll get into some of the details of customizing JSON-B later on in this article.

Deserialization with JSON-B is equally simple. Given an instance of Jsonb, we call one of the overloaded fromJson() methods and pass it the JSON document to serialize, along with the object type, as shown in Listing 4.

Listing 4. Deserializing a JSON string
Book book = jsonb.fromJson(json, Book.class);

While the JSON-B specification does not mandate preserving roundtrip equivalence, it does recommend it. While there is no guarantee, in most cases feeding the JSON output of a serialization into the deserialized fromJson() method will result in an equivalent object to the original.

JSON-B's default settings

For convenience, a Jsonb instance comes pre-configured with defaults reflecting accepted standards. Below is an overview of JSON-B’s default strategies and mappings. Later articles in the series will explore some of these in more depth.

  • Property naming strategy determines how a class’s field will be represented in serialized JSON. By default, a JSON property name takes on the exact same name as the field in the class that it serializes.
  • Property visibility strategy determines how fields are accessed and the importance given to access modifiers on fields and bean methods. During both serialization and deserialization, JSON-B’s default behavior is to access only public fields and public accessor and mutator methods. If no public method or fields exist, the property is ignored.
  • Property order strategy specifies the order in which properties appear in output JSON. JSON-B’s default order is lexicographic.
  • Binary data strategy encodes binary data, using a byte array by default.
  • Null value strategy specifies that object fields with a null value are ignored and null values in collections are preserved. Null values in JSON properties set the corresponding object field value as null.

Three additional defaults are important to know about:

  • JSON-B respects the I-JSON profile, with three exceptions: JSON-B does not restrict the serialization of top-level JSON to non-object or array text; it does not serialize binary data with base64url encoding; and it does not enforce additional restrictions on time-based properties. It is also possible to switch-on strict compliance with I-JSON via a customization option.
  • JSON-B’s date format and locale setting will depend on your machine setting.
  • JSON data is presented without line feeds or indentations, and is encoded using UTF-8.

Type handling with JSON-B

The Java API for JSON Binding is RFC 7159-compatible for serialization and deserialization and supports all Java types and primitives. We’ll explore and use many of the following principles and techniques in this series:

  • Basic types: These include all primitives and their associated wrapper classes, Number and String:
    • During serialization, the toString() method is called on the instance field to produce a String value.
    • Fields of type Number are the exception, because Number uses the doubleValue() method to produce a primitive.
    • Primitives values are left untouched.
    • On deserialization, the appropriate parseXXX() method is called to perform conversion to the needed basic type.
  • Java SE types: These refer to the range of Optional classes (such as OptionalInt), the BigInteger and BigDecimal classes, and the URI and URL classes.
    • Optional values are converted to Strings by retrieving their internal type and following the object’s convention for transformation to a String or primitive. (An example would be calling intValue() on an instance of Integer.)
    • Empty optionals are treated as null values and are dealt with according to the customization setting for handling nulls.
    • The deserialization process takes advantage of the type’s construction methods that accept a String parameter. An example would be new BigDecimal("1234").
  • Date types: JSON-B supports all the standard Java date types including Calendar, TimeZone, and the java.time.* package. The format used during serialization is ISO_DATE, ISO_DATE_TIME, and ISO_INSTANT/LOCAL_*/ZONED_*/OFFSET_*. The API specification (section 3.5) has a detailed breakdown of the format used with each date type.
  • Arrays and collection types: It is possible to serialize and deserialize single and multidimensional arrays of all supported Java types and primitives. Null value elements are represented as null values in the resulting JSON or Java array.
  • JSON processing types: All JSON processing types are supported for serialization and deserialization.
  • Java classes: To be serializable, a Java class is expected to have a no-argument public or protected constructor, or to explicitly specify a method to use for custom object construction. Some rules to note:
    • A constraint is not required for serialization: JSON-B can deserialize public, protected, and protected static classes.
    • Anonymous classes are not supported: serializing an anonymous class produces JSON objects.
    • Other than those related to the Java basic, Java SE, Date/Time and Collection/Map types already mentioned, interfaces are not supported during deserialization. The runtime type is used for serialization.

Default mapping

Next we'll take a look at an example that includes many of the default settings and types introduced so far. Listing 5’s Magazine class presents a selection of Java types, primitives, and collections. Note that the internalAuditCode field has only a setter method.

Listing 5. Magazine class
public class Magazine {
   private String id;
   private String title;
   private Author author;
   private Float price;
   private int pages;
   private boolean inPrint;
   private Binding binding;
   private List<String> languages;
   private URL website;
   private String internalAuditCode; // Only has setter method
   private LocalDate published;
   private String alternativeTitle;
   // Plumbing code removed for brevity
}

The Magazine object is constructed in Listing 6 and contains null fields in the language array, and the alternativeTitle field is set to null.

Listing 6. Constructing the Magazine object
Magazine magazine = new Magazine();
magazine.setId("ABCD-1234");
magazine.setTitle("Fun with Java");
magazine.setAuthor(new Author("Alex", "Theedom"));
magazine.setPrice(45.00f);
magazine.setPages(300);
magazine.setInPrint(true);
magazine.setBinding(Binding.SOFT_BACK);
magazine.setLanguages(
    Arrays.asList("French", "English", "Spanish", null));
magazine.setWebsite(new URL("https://www.readlearncode.com"));
magazine.setInternalAuditCode("IN-675X-NF09");
magazine.setPublished(LocalDate.parse("01/01/2018", 
    DateTimeFormatter.ofPattern("MM/dd/yyyy")));
magazine.setAlternativeTitle("IN-675X-NF09");
String json = JsonbBuilder.create().toJson(magazine);

Listing 7 shows this object being serialized. When serializing, the null value in the array is preserved, while the null value in the alternativeTitle field results in the field’s exclusion.

Also notice that the internalAuditCode value is not included in the JSON output. It cannot be included because there is no getter method or public field to use in retrieving the value.

Listing 7. JSON representation of Magazine
{
  "author": {
    "firstName": "Alex",
    "lastName": "Theedom"
  },
  "binding": "SOFT_BACK",
  "id": "ABCD-1234",
  "inPrint": true,
  "languages": [
    "French",
    "English",
    "Spanish",
    null
  ],
  "pages": 300,
  "price": 45.0,
  "published": "2018-01-01",
  "title": "Fun with Java",
  "website": "https://www.readlearncode.com"
}

Custom mapping

JSON-B’s default mapping behavior should be sufficient for most simple scenarios. The API also presents a variety of ways to customize mapping behavior for more complex scenarios and needs.

There are two customization models, which you may use together or separately: the annotation model and the runtime model. The annotation model uses built-in annotations to mark a field where you want to customize behavior. The runtime model builds a configuration strategy that is set on the Jsonb instance. Annotations and runtime configuration can be applied to both serialization and deserialization.

Customizing JSON-B with annotations

Using annotation, you would mark the field, JavaBean property, type, or package whose associated mapping behavior you wished to customize. Here is an example:

Listing 8. Annotation a field
@JsonbProperty("writer")
private Author author;

The annotation on the author field changes the name of the property in the JSON output from author to writer.

Customizing JSON-B with runtime configuration

The runtime model builds an instance of JsonbConfig, which it passes to the create() method of JsonBuilder as shown in Listing 9.

Listing 9. Build a Jsonb configuration instance
JsonbConfig jsonbConfig = new JsonbConfig()
       .withPropertyOrderStrategy(PropertyOrderStrategy.REVERSE)
       .withNullValues(true);

Jsonb jsonb = JsonbBuilder.create(jsonbConfig);

In this example, the configuration sets the property order to reverse and preserves all null values.

Basic customization

Although you can satisfy most simple use cases with out-of-the-box configurations, default mapping will only take you so far. In situations where you require more control over the serialization and deserialization process, JSON-B offers a range of customization options.

Customizing property names and ordering

JSON-B offers several property-related customizations that you should know about.

A property is excluded if the field is annotated @JsonbTransient, and a property’s name is changed with the @JsonProperty annotation. Naming strategies are set by passing a PropertyNamingStrategy constant to the withPropertyNamingStrategy() method of the JsonbConfig instance.

The order in which properties appear in the JSON output is set at the class level, using the @JsonbPropertyOrder() annotation or the withPropertyOrderStrategy() method. A PropertyOrderStrategy constant passed to the annotation and method determines the strategy.

Configure ignore properties and visibility

You can specify the visibility of named properties using either the @JsonbVisibility() annotation or the withPropertyVisibilityStrategy() method.

Null handling

There are three ways to change the default behavior of null handling:

  1. Annotate the package or class with the @JsonbNillable annotation.
  2. Annotate the relevant field or JavaBean property with @JsonbProperty and set the nillable parameter to true.
  3. Pass true or false to the withNullValues() method.

Listing 10 demonstrates the second and third approach, of annotating the class @JsonbNillable and setting nillable to true. You could use the class-level annotation, @JsonbNillable, to configure null handling for all fields, or the field-level annotation for finer grained control at the field level:

Listing 10. Configuring null handling
public class Magazine {
    @JsonbProperty(value = "writer", nillable = true)
    private Author author;
}

@JsonbNillable
    public class Magazine {
    private Author author;
}

Custom creator

Recall that JSON-B expects all classes to have a no-argument public constructor, which it uses to construct class instances. In cases where this is insufficient, you may use a custom constructor or static factory method instead. For either of these options you would use the annotation @JsonCreator.

Custom date and number formats

JSON-B specifies that the date format is set with @JsonbDateFormat() annotation and is passed the locale and date pattern to use. The annotation is used anywhere from the package to the field. Another option is to use the withDateFormat() method, as shown in Listing 11.

Listing 11. Date format
new JsonbConfig()
     .withDateFormat("MM/dd/yyyy", Locale.ENGLISH);

Handling binary data

JSON-B offers three strategies for handling binary data:

  • BYTE
  • BYTE_64
  • BYTE_64_URL

The method withBinaryDataStrategy() is passed one of these three static constants from the BinaryDataStrategy. Listing 12 shows an example.

Listing 12. Binary data strategy
new JsonbConfig()
     .withBinaryDataStrategy(BinaryDataStrategy.BASE_64_URL);

Configuring I-JSON

For the most part, default setting adhere to the I-JSON profile, save for the exceptions mentioned in the "Default settings" section previously. You can use the withStrictIJSON(true) method for scenarios requiring stricter compliance.

Advanced customization

In some circumstances neither annotations nor runtime configuration will help. For example, it’s often impossible to annotate third-party classes. Classes that don't have a default constructor are also troublesome. JSON-B offers two solutions for these scenarios: adapters and serializers/deserializers.

Customizing JSON-B with adapters

An adapter allows you to create custom Java objects and serialize JSON code. The adapter class must implement the JsonbAdapter interface and provide code for its two methods: adaptToJson() and adaptFromJson().

In Listing 13, the adaptToJson() method has been implemented with code that transforms the Booklet object shown in Listing 14 to a JSON string:

Listing 13. Implementing the adaptToJson() method
public JsonObject adaptToJson(Booklet booklet) throws Exception {
   return Json.createObjectBuilder()
        .add("title", booklet.getTitle())
        .add("firstName", booklet.getAuthor().getFirstName())
        .add("lastName", booklet.getAuthor().getLastName())
        .build();
}

Note that in Listing 13 we’ve used the JSON-P JsonObjectBuilder. Here is the Booklet object:

Listing 14. Booklet
public class Booklet {
   	private String title;
   	private Author author;
    // Plumbing code removed for brevity
}

As you can see, the adaptToJson() method "flattens" the Author object to two properties: firstName and secondName. This is just one of many examples of how to customize serialization using the adapter.

Registering a JSON-B adapter

You may register the JSON-B adapter using a JsonbConfig instance, like so:

new JsonbConfig().withAdapters(new bookletAdapter())

or with the following annotation on a given class:

@JsonbTypeAdapter(BookletAdapter.class)

Listing 15 shows the JSON document produced by this transformation.

Listing 15. Serialization of the Booklet class
{
  "title": "Fun with Java",
  "firstName": "Alex",
  "lastName": "Theedom"
}

The adaptFromJson() method is not shown; however, there is a code example in the GitHub repository associated with this article.

Customizing JSON-B with serializers and deserializers

JSON-B serializers and deserializers are the lowest level of customization available, giving you access to the parsers and generators found in the JSON Processing API.

To use this option, you must implement the JsonbDeserializer and JsonbSerializer and override the appropriate serialize() or deserialize() method. You would then write the custom code to do the needed work, placing it within the method.

The serializers and deserializers are registered with a JsonbConfig instance with the appropriate method: withDeserializers() or withSerializers(). See the GitHub repository for this article for examples of how to create and use JSON-B serializers and deserializers.

Conclusion

The expert group should be congratulated on the hard work they've done getting JSON-B ready for release in Java EE 8. This API completes Java EE’s suite of JSON APIs. In this article we've scratched the surface of the extensive JSON Binding API's capabilities. The next three articles will offer a more in-depth look at essential aspects of this most welcome addition to Java EE.

Test your knowledge

  1. Which of the following annotations, when used together, customize the JSON output to preserve null values, put the properties in reverse lexicographical order, and rename a property “cost”?
    1. @JsonbNillable
    2. @JsonbNullable
    3. @JsonbPropertyOrder(PropertyOrderStrategy.REVERSE)
    4. @JsonbPropertyOrder(PropertyOrderStrategy.RESERVED)
    5. @JsonbPropertyName("cost")
    6. @JsonbProperty("cost")
  2. What combination of the following JsonbConfig builder methods should you call to construct a custom configuration that: uses the strict I-JSON profile, specifies the date format as month/day/year and the English locale, and outputs JSON in a prettified format?
    1. withNonStrictIJSON(false)
    2. withStrictIJSON(true)
    3. withDateFormat(Locale.ENGLISH, "MM/dd/yyyy")
    4. withDateFormat("MM/dd/yyyy", Locale.ENGLISH)
    5. withFormatting(true)
    6. withPrettyFormatting(true)
  3. Which of the following ways can you use to specify the adapter to use, given the name of the adapter is OrderAdapter.class?
    1. new JsonbConfig().withAdapters(new orderAdapter())
    2. new JsonbConfig().withTypeAdapters(new orderAdapter())
    3. @JsonbTypeAdapter(OrderAdapter.class)
    4. @JsonbAdapter(OrderAdapter.class)
    5. None of the above
  4. What is the default visibility configuration for serialization and deserialization?
    1. public accessor and mutator methods only
    2. public accessor and mutator methods and fields only
    3. public fields only
    4. public and private fields
    5. public and protected accessor and mutator methods and fields
  5. Which of the following options could you use to customize the creation of an object from a JSON document?
    1. A custom constructor annotated: @JsonCreator
    2. A static factory method annotated: @JsonCreator
    3. A JSON B adapter
    4. A JSON B serializer
    5. It’s not possible

Check your answers!


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java development
ArticleID=1051935
ArticleTitle=Get started with the JSON Binding API, Part 1: The JSON Binding API in a nutshell
publish-date=11102017