15th December 2016

Getting started with JSON-B

Java API for JSON Binding or JSON-B is one of the newest APIs that’s going to be a part of Java EE 8 and has already passed the Public Review Ballot. As you may guess from its name, the JSON-B is trying to standardize the way Java objects would be serialized (presented) in JSON and defines a standard API in this regards. 

In this post, I’ll try to demonstrate core features and functionalities provided by this API so that you can have an idea about how to utilize it in your next project.

What you’ll learn in this post?

  • Setting up a project to use JSON-B
  • Reference Implementation of JSON-B
  • Serializing/Deserializing an object to/from JSON
  • The way primitive data types are mapped
  • Serializing Collection and Map implementation

Setup project dependencies

Currently the artifact containing the JSON-B API (javax.json.bind-api-1.0-SNAPSHOT.jar) is hosted on maven.java.net repository. Also the only reference implementation of JSON-B API (named “yasson“) has a dedicated snapshot repository hosted on repo.eclipse.org. So in order to have these artifacts defined as a dependency in your pom file, you have to add these repositories to your pom.xml.

<dependencies>
    <dependency>
        <groupId>javax.json.bind</groupId>
        <artifactId>javax.json.bind-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse</groupId>
        <artifactId>yasson</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>javax.json</artifactId>
        <version>1.1.0-SNAPSHOT</version>
    </dependency>
</dependencies> 
<repositories> <!-- Needed for JSON-B API -->
<repository>
    <id>java.net-Public</id>
    <name>Maven Java Net Snapshots and Releases</name>
    <url>https://maven.java.net/content/groups/public/</url>
</repository> <!-- Needed for Yasson -->
<repository>
    <id>yasson-snapshots</id>
    <name>Yasson Snapshots repository</name>
    <url>https://repo.eclipse.org/content/repositories/yasson-snapshots</url>
</repository>
</repositories>

Note: As Yasson (JSON-B reference implementation) internally relies on JSON-P API, a dependency to JSON-P reference implementation is added to the classpath. This may not be needed if you use another implementation of JSON-B in future or if you’re going to deploy your application on an application server that already provides this dependency at runtime.

JSON-B API

The JSON-B APIs are provided under javax.json.bind package. The most important interface is Jsonb that could be instantiated through a builder class named JsonbBuilder. When you have a reference to this class, you can call one of toJson or fromJson methods to serialize/deserialize objects to/from JSON string.

Shape shape = new Shape();
shape.setArea(12);
shape.setType("RECTANGLE");

Jsonb jsonb = JsonbBuilder.create();

// serialize an object to JSON
String jsonString = jsonb.toJson(shape); // output : {"area" : 12, "type" : "RECTANGLE"}

// deserialize a JSON string to an object
Shape s = jsonb.fromJSON("{\"area\" : 12, \"type\": \"TRIANGLE\"}");

Note that, for the deserialization process, the class should have a default constructor or else you’ll get an exception.

That’s it. The surface area of the JSON-B API is so small that there is almost no complexity involved. All other APIs and annotations provided under ‘javax.jsonb’ package (which are few in number) are only used for customizing the serialization/deserialization process.

Basic Java types mapping

The way Java primitive types and their corresponding wrapper classes are serialized to JSON, follows the conversion process defined for their ‘toString’ method documentation. Likewise, for the deserialization process, the conversion process defined for their ‘parse[TYPE]’ method (parseInt, parseLong, etc.) is used. However it’s not necessary for a reference implementation to call such methods, just to obey their conversion process.

To demonstrate how these types are mapped to JSON, consider a class named Apartment with following structure:

public class Apartment {

    private boolean rented;
    private byte rooms;
    private int price;
    private long id;
    private float loan;
    private double area;
    private char district;
    private String ownerName;

    /** getter and setters */
}

Following snippet tries to serialize an instance of this class.

public static void main(String[] args) {

    Apartment apartment = new Apartment();
    apartment.setRented(true);
    apartment.setRooms((byte) 4);
    apartment.setPrice(7800000);
    apartment.setId(234L);
    apartment.setLoan(3580000.4f);
    apartment.setArea(432.45);
    apartment.setDistrict('S');
    apartment.setOwnerName("Nerssi");

    String apartmentJSON = jsonb.toJson(apartment);
    System.out.printf(apartmentJSON);
}

which results to following JSON in output (comments are added to make it more readable):

{
  "area":       432.45      // double 
  "district":   "S",        // char 
  "id":         234,        // long 
  "loan":       3580000.5,  // float 
  "ownerName":  "Nerssi",   // String 
  "price":      7800000,    // int 
  "rented":     true,       // boolean 
  "rooms":      4           // byte 
}

As can be seen, the value used for each field is exactly the same as calling ‘toString’ method on its corresponding wrapper class. Also the String and Character classes are both converted to a UTF-8 string.

Mapping Dates

JSON-B spec defines a standard mapping for almost all the date and time related classes in JDK. The formats are based on ISO 8601. Parsing and formatting date values based on this ISO standard are already implemented in JDK in java.time.format.DateTimeFormatter. In a class definition that follows, you can find a property for every date and time classes defined in JDK along with its corresponding serialization format used in JSON-B:

public class DateDemo {

	private java.util.Date date;                            // ISO_DATE (or ISO_DATE_TIME if time value specified) 
	private java.util.Calendar calendar;                    // ISO_DATE (or ISO_DATE_TIME if time value specified) 
	private java.util.GregorianCalendar gregorianCalendar;  // ISO_DATE (or ISO_DATE_TIME if time value specified) 
	private java.util.TimeZone timeZone;                    // NormalizedCustomID as specified in java.util.TimeZone 
	private java.util.SimpleTimeZone simpleTimeZone;        // NormalizedCustomID as specified in java.util.TimeZone 
	private java.time.Instant instant;                      // ISO_INSTANT 
	private java.time.Duration duration;                    // java.time.Duration is the ISO 8601 seconds based representation 
	private java.time.Period period;                        // ISO 8661 Period representation 
	private java.time.LocalDate localDate;                  // ISO_LOCAL_DATE 
	private java.time.LocalTime localTime;                  // ISO_LOCAL_TIME 
	private java.time.LocalDateTime localDateTime;          // ISO_LOCAL_DATE_TIME 
	private java.time.ZonedDateTime zonedDateTime;          // ISO_ZONED_DATE_TIME 
	private java.time.ZoneId zoneId;                        // ZoneID as specified in java.time.ZoneId 
	private java.time.ZoneOffset zoneOffset;                // Zone offset as specified in java.tim.ZoneOffset 
	private java.time.OffsetDateTime offsetDateTime;        // ISO_OFFSET_DATE_TIME 
	private java.time.OffsetTime offsetTime;                // ISO_OFFSET_TIME 
	
	/* getters and setters */
}

We can instantiate and serialize an instance of this class as below:

	public static void main(String[] args) {
		DateDemo dateDemo = new DateDemo();
		dateDemo.setDate(new Date());
		dateDemo.setCalendar(Calendar.getInstance());
		dateDemo.setGregorianCalendar(GregorianCalendar.from(ZonedDateTime.now()));
		dateDemo.setTimeZone(TimeZone.getDefault());
		dateDemo.setSimpleTimeZone(new SimpleTimeZone(3600000, "Europe/Paris", Calendar.MARCH, -1, 
				Calendar.SUNDAY, 3600000, SimpleTimeZone.UTC_TIME, Calendar.OCTOBER, -1, 
				Calendar.SUNDAY, 3600000, SimpleTimeZone.UTC_TIME, 3600000));
		dateDemo.setInstant(Instant.now());
		dateDemo.setDuration(Duration.ofDays(4L));
		dateDemo.setPeriod(Period.ofMonths(3));
		dateDemo.setLocalDate(LocalDate.now());
		dateDemo.setLocalTime(LocalTime.now());
		dateDemo.setLocalDateTime(LocalDateTime.now());
		dateDemo.setZonedDateTime(ZonedDateTime.now());
		dateDemo.setZoneId(ZoneId.systemDefault());
		dateDemo.setZoneOffset(ZoneOffset.of("+03:30"));
		dateDemo.setOffsetDateTime(OffsetDateTime.now());
		dateDemo.setOffsetTime(OffsetTime.now());

		System.out.println(jsonb.toJson(dateDemo));
	}

Which results in the following JSON:

{
  "date":               "2016-07-27T23:07:41",
  "calendar":           "2016-07-27T23:07:41.782+02:00[Europe/Copenhagen]",
  "gregorianCalendar":  "2016-07-27T23:07:41.801+02:00[Europe/Copenhagen]",
  "timeZone":           "Europe/Copenhagen",
  "simpleTimeZone":     "Europe/Paris",
  "instant":            "2016-07-27T21:07:41.806Z",
  "duration":           "PT96H",
  "period":             "P3M",
  "localDate":          "2016-07-27",
  "localTime":          "23:07:41.807",
  "localDateTime":      "2016-07-27T23:07:41.807",
  "zonedDateTime":      "2016-07-27T23:07:41.807+02:00[Europe/Copenhagen]",
  "zoneId":             "Europe/Copenhagen",
  "zoneOffset":         "+03:30",
  "offsetDateTime":     "2016-07-27T23:07:41.807+02:00",
  "offsetTime":         "23:07:41.807+02:00",
}

Note that the order of properties in the output would not necessarily be the same as the one in the code. I’ve changed the order of properties in the above JSON fragment to be the same as its corresponding code snippet to make it easier to read and compare with its corresponding code. However you can customize the ordering of properties in the output by JSON-B annotations. Also I’ve aligned the values to make them more readable.

Mapping Collections

JSON-B has a comprehensive support for almost all collection and map classes available in java.util as long as they provide a default no-argument constructor. Map based classes (e.g HashMap, TreeMap, NavigableMap, etc. that implement java.util.Map interface) would be serialized as an object that its properties correspond to map keys and have their values from the map. Classes extending from java.util.Collection (e.g. Set, List, HashedSet, LinkedList, ArrayList, etc.) are mapped to JSON arrays on the output.

In the following snippet, the Blog class has a set of Post objects and each Post has a map of comments in which the name of the person who made the comment is the key and the comment text is the value.

public class Blog {
	private String name;
	private String address;
	private List<Post> posts = new ArrayList<>();

	public Blog() { }

	public Blog(String name, String address) {
		this.name = name;
		this.address = address;
	}

	/** getter and setter methods */
}

public class Post {
	private String subject;
	private String body;
	private Date postDate;
	private Map<String, String>
			comments = new HashMap<>();

	public Post() { }

	public Post(String subject, String body, Date postDate) {
		this.subject = subject;
		this.body = body;
		this.postDate = postDate;
	}

	/** getter and setter methods */
}

No if we try to instantiate and initialize a Blog as follow:

public static void main(String[] args) {
		Blog javaindeed = new Blog("Java Indeed", "www.javaindeed.com");
		
		Post post1 = new Post("First post", "This is a sample content for the first post", new Date());
		post1.getComments().put("User1", "Great thanks :)");
		post1.getComments().put("User2", "Horrible :|");
		javaindeed.getPosts().add(post1);
		
		Post post2 = new Post("Second post", "This is another post content", new Date());
		post2.getComments().put("User3", "What if lablabla");
		post2.getComments().put("User4", "I'm agree also !!!");
		post2.getComments().put("User5", "So what !?");
		javaindeed.getPosts().add(post2);
		
		Post post3 = new Post("Third post", "And the final post just for test ;)", new Date());
		javaindeed.getPosts().add(post3);
		
		Jsonb jsonb = JsonbBuilder.create();
		
		System.out.println(jsonb.toJson(javaindeed));
	}

The final output would be:

{
  "address": "www.javaindeed.com",
  "name": "Java Indeed",
  "posts": [
    {
      "body": "This is a sample content for the first post",
      "comments": {
        "User2": "Horrible :|",
        "User1": "Great thanks :)"
      },
      "postDate": "2016-12-14T23:29:05",
      "subject": "First post"
    },
    {
      "body": "This is another post content",
      "comments": {
        "User5": "So what !?",
        "User4": "I'm agree also !!!",
        "User3": "What if lablabla"
      },
      "postDate": "2016-12-14T23:29:05",
      "subject": "Second post"
    },
    {
      "body": "And the final post just for test ;)",
      "comments": {
      },
      "postDate": "2016-12-14T23:29:05",
      "subject": "Third post"
    }
  ]
}

As can be seen, empty the posts property is mapped to a JSON array of objects in which each object has a comments property that is generated from the map. For the “Third post”, as there is no comment, the map is serialized as an empty object (a pair of {}). This is due to the fact that the Map property inside Post class is always initialized to an empty HashMap and it’s not a null.

Conclusion

JSON-B is one of the first JSRs (among those that supposed to be part of Java EE 8) that its specification is almost ready and its only RI (Yasson) is fully implemented. Now its time to polish it, stabilize it and make it ready to be integrated in different projects, frameworks and application servers. This was just a short review of some basic features of this JSR. For more information please visit www.json-b.net.

Please follow and like us:

You may also like...

Leave a Reply

Your email address will not be published.