Showing posts with label GSON. Show all posts
Showing posts with label GSON. Show all posts

Saturday, 23 June 2012

Practical Example of GSON (Part 2)

Please note that this page has moved to: http://www.javacreed.com/gson-deserialiser-example/.

The first article I wrote about GSON (part 1) is (at the time of writing) the most popular article in my small blog. Thus, I decided to write a second article and add more practical and advance examples which anyone can use. As also noted in my previous article, the official GSON site is: http://sites.google.com/site/gson/.

In this article we will see how to parse complex JSON objects into existing Java objects that do not necessary have the same structure as the JSON object. We will see how the use of the GSON deserialiser (JsonDeserializer) in order to control how the JSON object maps to the Java object.

The code shown here is all available at: http://code.google.com/p/gson-practical-examples/source/checkout.

The readers are encouraged to first read part 1 before proceeding, unless they are already familiar with GSON.

A Simple Example

Let's say we have the following JSON object, where it contains four Java books titles written by various, well known, authors.

{
    '1': 'Effective Java (2nd Edition)',
    '2': 'JavaTM Puzzlers: Traps, Pitfalls, and Corner Cases',
    '3': 'Java Concurrency in Practice',
    '4': 'Java: The Good Parts'
}

Note that each name/value pair has a number as its name and the book title as its value. Using the methods discussed in part 1 would create a problem. In that article, Gson is expecting to find variable names in Java with the same name as that found in JSON. But names in Java cannot start with a number. They can contain a number, but cannot start with one (as described in chapter 6 of the Java Language Specification).

So how can we parse this JSON object and use it in Java?
We can use the JsonDeserializer to parse the JSON object into our Java object the way we want it. Using the JsonDeserializer, we have full control over how JSON is parsed as we will see in the following example.

Consider the following simple Java object.


package com.albertattard.examples.gson.part2_1;
import java.util.ArrayList;
import java.util.List;

public class Books {

  private List<string> booksTitles = new ArrayList<>();

  public void addBookTitle(String title) {
    booksTitles.add(title);
  }

  @Override
  public String toString() {
    return booksTitles.toString();
  }
}

This Java object will be used to hold the books listed in the JSON object shown earlier. Note that JSON object has four fields, one for each book, while the Java object has a list in which these books are saved. The structure of these two objects (Java and JSON) is different.

In order to be able to parse JSON to Java we need to create our own instance of the JsonDeserializer interface as shown next.


package com.albertattard.examples.gson.part2_1;
import java.lang.reflect.Type;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;

public class BooksDeserializer implements JsonDeserializer<Books> {

  @Override
  public Books deserialize(final JsonElement json, 
        final Type typeOfT, 
        final JsonDeserializationContext context) 
        throws JsonParseException {

    Books books = new Books();
    // Parsing will be done here.
    return books;
  }
}

The above example is not complete and we still need to add the most important thing, which is the parsing. But let's understand this class before we make it more complex by adding more code to it. The interface JsonDeserializer requires a type, which is the type of object that we will be parsing. In this case, we are parsing JSON into the Java object of type Books. The return type of the deserialize() method must be of the same type as the interface parameter, Books.

How does this work?
Gson will parse the JSON object into a Java object of type JsonElement. The JsonElement can be thought of a tree of name/value pairs containing all elements found in the JSON object. Each child within the JsonElement is yet another JsonElement. In other words we have a tree of JsonElements. Through this object we can retrieve each JSON element by its name and set the Java object accordingly. The following example shows how we can retrieve the first book listed in the JSON object and add it to the Java object.


package com.albertattard.examples.gson.part2_1;
import java.lang.reflect.Type;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;

public class BooksDeserializer implements JsonDeserializer<Books> {

  @Override
  public Books deserialize(final JsonElement json, 
        final Type typeOfT, 
        final JsonDeserializationContext context) 
        throws JsonParseException {
    Books books = new Books();

    JsonObject jsonObject = json.getAsJsonObject();
    books.addBookTitle(jsonObject.get("1").getAsString());

    return books;
  }
}

In the above example, we are retrieving the JSON element with name "1" using the following code fragment:

jsonObject.get("1")

This returns the JsonElement with the name: "1". In this case it is a simple String. Note that in order to retrieve the actual value we need to invoke another method on the JsonElement instance, which returns the value we need, String in this case.

jsonObject.get("1").getAsString()

The rest of the books titles can be retrieved in the same manner.

Before we can utilise our new deserializer, we must instruct GSON to use our deserializer when parsing objects of type Books, as shown in the next code example.


package com.albertattard.examples.gson.part2_1;
import java.io.InputStreamReader;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class Main {
  public static void main(String[] args) throws Exception {
    // Configure GSON
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Books.class,
        new BooksDeserializer());
    Gson gson = gsonBuilder.create();

    // The JSON data
    Reader data = new InputStreamReader(
        Main.class.getResourceAsStream("books.json"), "UTF-8");

    // Parse JSON to Java
    Books books = gson.fromJson(data, Books.class);
    System.out.println(books);
  }
}

In the above example, we are creating an instance of Gson through the GsonBuilder. Using the registerTypeAdapter() method, we are registering our deserializer and instructing GSON to use our deserializer when deserializing objects of type Books. When we request GSON to deserialize an object to the Books class, GSON will use our deserializer . The following list describes what happens when we invoke: gson.fromJson(data, Books.class).
  1. Parse the input as JsonElement. At this stage, the string JSON object is changed into a generic Java object of type JsonElement. This step also ensures that the given JSON data is valid.
  2. Find the deserializer for the given object, in this case the BooksDeserializer instance.
  3. Invokes the method deserialize() and provides the necessary parameters. In his example, our deserialize() will be invoked. Here an object of type Books is created from the given JsonElement object. This is from Java to Java conversion.
  4. Returns the object returned by the deserialize() method to the caller of the fromJson() method. This is like a chain, where GSON receives an object from out deserializer and returns it to its caller.
Running the above example would print the following:

[Effective Java (2nd Edition), JavaTM Puzzlers: Traps, Pitfalls, and Corner Cases, Java Concurrency in Practice, Java: The Good Parts]

This concludes our simple example. This example acts as a primer for other complex parsing. For example, parsing JSON objects that include nested objects, arrays and the like. In the next example we will discuss an enhanced version of the objects discussed here.

Nested Objects

In this example we will describe how to parse nested objects, that is, objects within other objects. Here we will introduce a new entity, the author. A book, together with the title and ISBN can have a list of authors. On the other hand every author can have many books. The JSON object that will be using in this example differs from the previous one to cater for the new entity as shown next:

{
  '1': {
    'title': 'Effective Java (2nd Edition)',
    'isbn': '978-0321356680',
    'authors': ['Joshua Bloch']
  },
  '2': {
    'title': 'JavaTM Puzzlers: Traps, Pitfalls, and Corner Cases',
    'isbn': '978-0321336781',
    'authors': ['Joshua Bloch', 'Neal Gafter']
  },
  '3': {
    'title': 'Java Concurrency in Practice',
    'isbn': '978-0321349606',
    'authors': ['Brian Goetz', 'Tim Peierls', 'Joshua Bloch', 
                'Joseph Bowbeer', 'David Holmes', 'Doug Lea']
  },
  '4': {
    'title': 'Java: The Good Parts',
    'isbn': '978-0596803735',
    'authors': ['Jim Waldo']
  }
}

We still have our four books, only this time we have a more complex and detailed JSON object. Instead of a simple book title, we also have an ISBN and an array of authors.

The new example provides new challenges. One of the authors, Joshua Bloch, has three books. This immediately leads to the following question.

How many instance of this author should we have?
There are two possible answers for this question: just one or three (one for every book). There is no one correct answer, and both cases can be valid. In our examples we are going to have one instance of the author even when he or she has more than one book. We are taking this approach as this approach resembles the reality and helps highlighting the goal of this article (GSON examples). Therefore we will have one author object representing the author Joshua Bloch.

For this example we will be using three domain objects:

  • Author
  • Book
  • Books
All three objects have references to the other objects. For example, the Author class has a list of Books and the Book has a list of Authors. The Books class contains all parsed objects. The Books class also provides the functionality required to maintain one instance for each author as we will see later on in this example.

Author


package com.albertattard.examples.gson.part2_2;
import java.util.HashSet;
import java.util.Set;

public class Author {

  private Set<Book> books = new HashSet<>();
  private String name;

  public Author(final String name) {
    this.name = name;
  }

  public void addBook(Book book) {
    books.add(book);
  }

  public Set<Book> getBooks() {
    return books;
  }

  public String getName() {
    return name;
  }

  @Override
  public String toString() {
    return String.format("%s has %d book(s)", name, books.size());
  }
}

Book


package com.albertattard.examples.gson.part2_2;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class Book {

  private Set<Author> authors;
  private String isbn;
  private String title;

  public Book(String title, String isbn, Author... authors) {
    this.title = title;
    this.isbn = isbn;
    this.authors = new HashSet<>(Arrays.asList(authors));
  }

  public Set<Author> getAuthors() {
    return authors;
  }

  @Override
  public String toString() {
    StringBuilder fomrattedString = new StringBuilder();
    fomrattedString.append(title).append(" (").append(isbn)
        .append(")");

    fomrattedString.append(" by: ");
    for (Author author : authors) {
      fomrattedString.append(author.getName()).append(", ");
    }

    // To remove the last comma followed by a space
    return fomrattedString.
             substring(0, fomrattedString.length() - 2);
  }
}

Books


package com.albertattard.examples.gson.part2_2;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class Books {

  // A map of authors index by their name.
  private Map<String, Author> authors = new HashMap<>();
  private Set<Book> books = new HashSet<>();

  public void addAuthor(Author author) {
    authors.put(author.getName(), author);
  }

  public void addBook(Book book) {
    books.add(book);
  }

  public Author getAuthorWithName(String name) {
    return authors.get(name);
  }

  @Override
  public String toString() {
    StringBuilder formattedString = new StringBuilder();
    for (Author author : authors.values()) {
      formattedString.append(author).append("\n");
      for (Book book : author.getBooks()) {
        formattedString.append("  ").append(book).append("\n");
      }
      formattedString.append("\n");
    }

    return formattedString.toString();
  }
}





Coming soon.
This article is not complete and more information will follow shortly.

Tuesday, 30 June 2009

Practical Example of GSON

Please note that this page has moved to: http://www.javacreed.com/simple-gson-example/.

GSON is a Java API, developed by Google, used to convert between Java objects and JSON objects. This article discusses and provides examples about this API and how it can be used. More information about this API can be found at: http://sites.google.com/site/gson/.

This is the first from three articles about GSON. No GSON or JSON experience is required as this article acts as a primer for the other two articles. The second article provides more examples about the use of GSON deserializer (from JSON to Java) and the third and final article provides more examples about the GSON serializer (from Java to JSON). All code for all three articles is available at: http://code.google.com/p/gson-practical-examples/source/checkout.

Download and Install

Before you can do any work with this API, you need to download the library (jar file) and include it in the classpath. The library, together with the source and JavaDocs, can be downloaded from: http://code.google.com/p/google-gson/downloads/list. Once downloaded, add the gson-<version>.jar to the classpath. For those readers who prefer to use maven to manage the dependencies (JAR files), add the following dependency to the pom.xml.

    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.2.1</version>
    </dependency>

Change the <version>2.2.1</version> as required. All code examples shown in this article use the version listed above. A copy of the pom.xml file can be found here.

If this library is to be used in a web application, make sure to have a copy saved under the WEB-INF/lib folder. Alternatively, the library can be added to the application server and made available to the web application.

A Simple Example

The GSON API provides a stateless class, Gson, that handles the conversions between Java and JSON objects. An instance of this class can be created by invoking the default constructor or as shown in the simple example below, using the GsonBuilder class. The GsonBuilder class provides customisation and allows the developer to instantiate Gson as required.

package com.albertattard.examples.gson.part1_1;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class SimpleExample1 {
  public static void main(String[] args) {
    Gson gson = new GsonBuilder().create();
    gson.toJson("Hello", System.out);
    gson.toJson(123, System.out);
  }
}
In the above example, we've created an instance of Gson and converted the Java String and int to JSON objects. The above code produces the following as its output to the command prompt:
"Hello"123
It's not rocket science, but it's a start. Note that the output of the above is all going to the command prompt. The toJason() method takes two parameters, the Java object to be converted to JSON and an instance of Appendable. We can easily change the out to a file or network stream.

package com.albertattard.examples.gson.part1_1;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class SimpleExample2 {
  public static void main(String[] args) throws IOException {
    Writer writer = new FileWriter("Output.json");

    Gson gson = new GsonBuilder().create();
    gson.toJson("Hello", writer);
    gson.toJson(123, writer);

    writer.close();
  }
}
Why is the variable declared as a Writer when the actual type is FileWriter?
It is a good practice to have your variables as generic as possible. In the above example, we're only using methods defined by the Appendable and Writer interfaces. Having the variable type more specific than required will make the code less portable and harder to maintain as we'll see in the following example.

Note that in the above example we are not handling the steams (Writer) properly. Ideally the resources are closed within the finally block or used within the try-with-resources as shown below. We've ignored this to keep the code as simple as possible.


  public static void main(String[] args) throws IOException {
    try (Writer writer = new FileWriter("Output.json")) {

      Gson gson = new GsonBuilder().create();
      gson.toJson("Hello", writer);
      gson.toJson(123, writer);
    }
  }

The above code produces the file: Output.json with the JSON objects. Note that here we used the character streams and not the byte streams. We cannot use the byte streams as the toJson() method is expecting an Appendable instance and the byte streams are not descendants from the Appendable interface. The Appendable interface works with characters and not bytes. Java provides the InputStreamReader and the OutputStreamWriter classes that convert byte streams into character streams as illustrated in the following example.


package com.albertattard.examples.gson.part1_1;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class SimpleExample3 {

  public static void main(String[] args) throws IOException {
    Writer writer = new OutputStreamWriter(
            new FileOutputStream("Output.json"));

    Gson gson = new GsonBuilder().create();
    gson.toJson("Hello", writer);
    gson.toJson(123, writer);

    writer.close();
  }
}
As you can see, we only needed to change the instantiation part. The rest (expect for the imports) of the code is unchanged.

Consuming JSON objects

Let's say that we need to consume JSON objects and load them as Java objects. Assume that a web server will produce the following JSON when queried:

{
  NAME:"Albert Attard",
  P_LANGUAGE:"Java",
  LOCATION:"Malta"
}
This JSON object contains three fields with their respective values. Let's say that we need to consume the JSON object and create a Java object that represents this data. To make this example more interesting, let assume that we're only interested from the name and the location fields.

First we need to create a Java class with the fields that we want to represent (name and location). Let's call the class Person1. The name of this class is irrelevant, but the name of the fields is not. The field names must match (including the case) with the names in JSON. Also, the class must include a default constructor. As shown below, the fields name and location are in uppercase as found in JSON. The JSON field P_LANGUAGE is ignored as the Java object does not include a field with this name. It is understandable that the fields' names do not follow the Java naming convention, but for the time being let's keep things simple. More about this is discussed in part 2.


package com.albertattard.examples.gson.part1_2;

public class Person1 {
  private String NAME;
  private String LOCATION;

  // Getters and setters are not required for this example.
  // GSON sets the fields directly.

  @Override
  public String toString() {
    return NAME + " - " + LOCATION;
  }
}
With the Java object ready, we can read the JSON objects and load them as Java objects as illustrated below. To simulate a real life situation, we're using a byte stream as input with default encoding. Also note that the JSON content is saved into a file (which is not usually the case) located in the same directory of the following class.

package com.albertattard.examples.gson.part1_2;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class JsonToJava1 {

  public static void main(String[] args) throws IOException {
    Reader reader = new InputStreamReader(JsonToJava1.class
        .getResourceAsStream("Server1.json"));

    Gson gson = new GsonBuilder().create();
    Person1 p = gson.fromJson(reader, Person1.class);
    System.out.println(p);

    reader.close();
  }
}
This should produce: Albert Attard - Malta. Gson parsed the JSON object and created an instance of the Person1 class.

Nested JSON Objects

Let's take the above example one step further and include a nested object as illustrated in the following JSON code fragment.

{
  NAME:"Albert Attard",
  P_LANGUAGE:"Java",
  LOCATION:"Malta",
  EXAM: {
    SUBJECT:"Programming",
    GRADE:4.5
  }
}
Here we have an EXAM field which is made from two other fields: SUBJECT and GRADE. Likewise, we need to modify the Person1 class defined above to include the EXAM field and create a new Java class to represent the SUBJECT and GRADE fields.

We first create the new class that will represent the nested object. As we discussed before, the class name is irrelevant but the fields' names must match those define in JSON.


package com.albertattard.examples.gson.part1_3;

public class Exam1 {
  private String SUBJECT;
  private double GRADE;

  // Getters and setters are not required for this example.
  // GSON sets the fields directly.

  @Override
  public String toString() {
    return SUBJECT + " - " + GRADE;
  }
}
Now we can modify the Person1 class (Person2 in the following example) and include a new field with the same name as in JSON of type Exam1 as shown next. Instead we created a new class to keep track of the progress made.

package com.albertattard.examples.gson.part1_3;

public class Person2 {

  private String NAME;
  private String LOCATION;
  private Exam1 EXAM; 

  @Override
  public String toString() {
    return NAME + " - " + LOCATION + " (" + EXAM + ")";
  }
}
Note that the changes required are minimal as Gson dynamically discovers (through reflection) the class and its fields. This article does not cover reflection. For more information about reflection please refer to: Reflection in Action.
Finally, let's test the new changes.

package com.albertattard.examples.gson.part1_3;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class JsonToJava2 {

  public static void main(String[] args) throws IOException {
    Reader reader = new InputStreamReader(JsonToJava2.class
        .getResourceAsStream("Server2.json"));

    Gson gson = new GsonBuilder().create();
    Person2 p = gson.fromJson(reader, Person2.class);
    System.out.println(p);

    reader.close();
  }
}
Instead of using person 1, we're using the new class which include the new exam field.

Conclusion

Even though it may be a new concept, JSON is very simple and straight forward. The Gson API makes it very simple to use and even though not discussed here, it provides a great deal of flexibility.

For more GSON examples please visit the second part (part-2), where we explore more complex examples and discuss how to use the GSON deserializer to take full control over the deserialization process.