GSON DESERIALISER EXAMPLE
来源:互联网 发布:淘宝店商品怎么分类 编辑:程序博客网 时间:2024/05/17 05:11
转自:http://www.javacreed.com/gson-deserialiser-example/
GSON DESERIALISER EXAMPLE
This article continues on a previous article, that described simple and basic use of 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
Java Doc) in order to control how the JSON object maps to the Java object.
Observation
Please note the we will use the terms parse or deserialise interchangeably in this article.
All code listed below is available at: https://java-creed-examples.googlecode.com/svn/gson/Gson Deserialiser Example. Most of the examples will not contain the whole code and may omit fragments which are not relevant to the example being discussed. The readers can download or view all code from the above link.
The readers are encouraged to first read the article Simple Gson Example before proceeding, unless they are already familiar with Gson.
A Simple Example
Let’s say we have the following JSON object, where it contains a popular Java book (Amazon) title written by two, well known, authors.
{ 'title': 'Java Puzzlers: Traps, Pitfalls, and Corner Cases', 'isbn-10': '032133678X', 'isbn-13': '978-0321336781', 'authors': ['Joshua Bloch', 'Neal Gafter']}
The above JSON comprise four fields, one of which is an array. These fields represent our book. Using the methods discussed in theSimple Gson Example article would create a problem. By default, Gson expects to find variable names in Java with the same name as that found in JSON. Therefore, we should have a class with the following field names: title
, isbn-10
, isbn-13
and authors
. But names in Java cannot contain the minus sign (-
) as described in the Java Language Specification (Chapter 6).
Using the JsonDeserializer
, we have full control over how JSON is parsed as we will see in the following example. Alternatively we can use annotations as described in Gson Annotations Example article. Annotations provide less control, but are simpler to use and understand. With that said, annotations have their limitations too and cannot address all problems described here.
Consider the following simple Java object.
package com.javacreed.examples.gson.part1;public class Book { private String[] authors; private String isbn10; private String isbn13; private String title; // Methods removed for brevity}
This Java object will be used to hold the book listed in the JSON object shown earlier. Note that JSON object has four fields, one for each field found in JSON. The structure of these two objects (Java and JSON) is the same in this example but this is not required. The Java object can have a different structure than that found in the corresponding JSON object.
In order to be able to parse JSON to Java we need to create our own instance of the JsonDeserializer
interface and register this with theGsonBuilder
(Java Doc). The following example shows our implementation of JsonDeserializer
.
package com.javacreed.examples.gson.part1;import java.lang.reflect.Type;import com.google.gson.JsonArray;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 BookDeserializer implements JsonDeserializer<Book> { @Override public Book deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { //The deserialisation code is missing final Book book = new Book(); book.setTitle(title); book.setIsbn10(isbn10); book.setIsbn13(isbn13); book.setAuthors(authors); return book; }}
The above example is not complete and we still need to add the most important thing, which is the deserialisation. 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 parsed. In this case, we are parsing JSON into the Java object of type Book
. The return type of the deserialize()
method must be of the same type as the generics parameter,Book
.
Gson will parse the JSON object into a Java object of type JsonElement
(Java Doc). An instance of JsonElement
can be one of the following:
JsonPrimitive
(Java Doc) – such as a string or integerJsonObject
(Java Doc) – a collection ofJsonElement
s indexed by thier name (of typeString
). This is similar to aMap<String, JsonElement>
(Java Doc)JsonArray
(Java Doc) – a collection ofJsonElement
s. Note that the array elements can be any of the four types and mixed types are supported.JsonNull
(Java Doc) – anull
value
The above image shows all types of JsonElement
. The JsonObject
can be thought of a collection of name/value pairs where the values are of type JsonElement
. The following image shows an example of JSON object hierarchy.
The above image shows a JSON object hierarchy with a JsonObject
as its root. It is important to note that, different from Java, JSON supports arrays of different types. In the above image, the JsonArray
comprises JsonObject
s, JsonArray
s and JsonPrimitive
s. Please note that the JSON object hierarchy shown above does not reflect the JSON object listed before. Following is the JSON object hierarchy for the JSON object listed before.
If we are to deserialise this JSON object, we first need to convert the given JsonElement
into a JsonObject
as shown next.
// The variable 'json' is passed as a parameter to the deserialize() methodfinal JsonObject jsonObject = json.getAsJsonObject();
A JsonElement
can be converted to any of the other types using a similar approach.
The elements within the a JsonObject
can be retrieved by their name. For example, to retrieve the title
element from the JSON listed above we can do the following.
// The variable 'json' is passed as a parameter to the deserialize() methodfinal JsonObject jsonObject = json.getAsJsonObject();JsonElement titleElement = jsonObject.get("title")
The object returned is not a String
but yet another JsonElement
. This can be converted to String
by invoking the getAsString()
as shown below.
// The variable 'json' is passed as a parameter to the deserialize() methodfinal JsonObject jsonObject = json.getAsJsonObject();JsonElement titleElement = jsonObject.get("title")final String title = jsonTitle.getAsString();
The following example shows how to convert the JSON object listed above using a custom deserializer.
package com.javacreed.examples.gson.part1;import java.lang.reflect.Type;import com.google.gson.JsonArray;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 BookDeserializer implements JsonDeserializer<Book> { @Override public Book deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { final JsonObject jsonObject = json.getAsJsonObject(); final JsonElement jsonTitle = jsonObject.get("title"); final String title = jsonTitle.getAsString(); final String isbn10 = jsonObject.get("isbn-10").getAsString(); final String isbn13 = jsonObject.get("isbn-13").getAsString(); final JsonArray jsonAuthorsArray = jsonObject.get("authors").getAsJsonArray(); final String[] authors = new String[jsonAuthorsArray.size()]; for (int i = 0; i < authors.length; i++) { final JsonElement jsonAuthor = jsonAuthorsArray.get(i); authors[i] = jsonAuthor.getAsString(); } final Book book = new Book(); book.setTitle(title); book.setIsbn10(isbn10); book.setIsbn13(isbn13); book.setAuthors(authors); return book; }}
In the above example, we are retrieving the JSON element and its four fields and returning an instance of Book
.
Before we can utilise our new deserializer, we must instruct Gson to use our deserializer when parsing objects of type Book
, as shown in the next code example.
package com.javacreed.examples.gson.part1;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(Book.class, new BookDeserializer()); Gson gson = gsonBuilder.create(); // The JSON data try(Reader reader = new InputStreamReader(Main.class.getResourceAsStream("/part1/sample.json"), "UTF-8")){ // Parse JSON to Java Book book = gson.fromJson(reader, Book.class); System.out.println(book); } }}
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 Book
. When we request Gson to deserialize an object to the Book
class, Gson will use our deserializer. The following steps describes what happens when we invoke: gson.fromJson(data, Book.class)
.
- Parse the input as
JsonElement
. Note that even though the type of the object isJsonElement
, this can be anything. At this stage, the string JSON object is deserialised into Java objects of typeJsonElement
s. This step also ensures that the given JSON data is valid. - Find the deserializer for the given object, in this case the
BookDeserializer
instance. - Invokes the method
deserialize()
and provides the necessary parameters. In this example, ourdeserialize()
will be invoked. Here an object of typeBook
is created from the givenJsonElement
object. This is from Java to Java conversion. - Returns the object returned by the
deserialize()
method to the caller of thefromJson()
method. This is like a chain, where Gson receives an object from our deserializer and returns it to its caller.
Running the above example would print the following:
Java Puzzlers: Traps, Pitfalls, and Corner Cases [ISBN-10: 032133678X] [ISBN-13: 978-0321336781]Written by: >> Joshua Bloch >> Neal Gafter
This concludes our simple example. This example acts as a primer for following, more complex, examples. In the next example we will discuss an enhanced version of the objects discussed here, where the authors are not a simple string but an object.
Nested Objects
In this example we will describe how to deserialise 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:
{ 'title': 'Java Puzzlers: Traps, Pitfalls, and Corner Cases', 'isbn': '032133678X', 'authors':[ { 'id': 1, 'name': 'Joshua Bloch' }, { 'id': 2, 'name': 'Neal Gafter' } ]}
The structure of the JSON object was changed slightly and instead of primitives we have the authors as JSON objects as shown in the following image.
We still have one book, only this time we have a more complex and detailed JSON object. Together with a name
, the author also has anid
. A new class called Author
is added to the model and the Book
class now uses it to save the author information. This immediately leads to the following question.
How should we deserialise the new Author
class?
There are several options.
- We can update the
BookDeserializer
and add the authors’ deserialisation code there. This has a limitation as it binds the deserialization of theAuthor
with that of theBook
, and thus is not recommended. - We can use the default Gson implementation, which will work well in this case as both the Java object (the
Author
class) and the JSON objects have the same fields’ names and can be deserialised as described in the article Simple Gson Example. - Alternatively, we can write an
AuthorDeserializer
class which will take care of the deserialisation of authors.
We will start with the second option, to keep the changes to a minimum and the example as simple as possible. Then we add the new deserializer to show the flexibility of Gson.
The JsonDeserializer
provides an instance of JsonDeserializationContext
(Java Doc) as the third parameter to the deserialize()
method. We have not yet used this parameter. We can delegate the deserialisation of objects to the given instance ofJsonDeserializationContext
. It will deserialise the given JsonElement
and returns an instance of the required type as shown below.
Author author = context.deserialize(jsonElement, Author.class);
The example shown above, is delegating the deserialisation of the Author
class to the context
. In turn it will try to locate an instance ofJsonDeserializer
that can deserialize it, if one is registered, otherwise it uses the default mechanism as described in the article titled:Simple Gson Example.
Our example uses an array of Author
s and thus we need to use the correct type as shown in the following example.
package com.javacreed.examples.gson.part2;import java.lang.reflect.Type;import com.google.gson.JsonArray;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 BookDeserializer implements JsonDeserializer<Book> { @Override public Book deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { final JsonObject jsonObject = json.getAsJsonObject(); final String title = jsonObject.get("title").getAsString(); final String isbn10 = jsonObject.get("isbn-10").getAsString(); final String isbn13 = jsonObject.get("isbn-13").getAsString(); // Delegate the deserialization to the context Author[] authors = context.deserialize(jsonObject.get("authors"), Author[].class); final Book book = new Book(); book.setTitle(title); book.setIsbn10(isbn10); book.setIsbn13(isbn13); book.setAuthors(authors); return book; }}
Converting from a JsonPrimitive
to a JsonObject
was very easy and straightforward as we saw in the above example.
Like the BookDeserialiser
, we can write the ArthurDeserialiser
and deserialise the author in a similar fashion we did with the book as shown next,
package com.javacreed.examples.gson.part2;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 AuthorDeserializer implements JsonDeserializer { @Override public Author deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { final JsonObject jsonObject = json.getAsJsonObject(); final Author author = new Author(); author.setId(jsonObject.get("id").getAsInt()); author.setName(jsonObject.get("name").getAsString()); return author; }}
In order to use the ArthurDeserialiser
we need to add this to the GsonBuilder
as shown next,
package com.javacreed.examples.gson.part2;import java.io.IOException;import java.io.InputStreamReader;import java.io.Reader;import com.google.gson.Gson;import com.google.gson.GsonBuilder;public class Main { public static void main(final String[] args) throws IOException { // Configure GSON final GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter(Book.class, new BookDeserializer()); gsonBuilder.registerTypeAdapter(Author.class, new AuthorDeserializer()); final Gson gson = gsonBuilder.create(); // Read the JSON data try (Reader reader = new InputStreamReader(Main.class.getResourceAsStream("/part2/sample.json"), "UTF-8")) { // Parse JSON to Java final Book book = gson.fromJson(reader, Book.class); System.out.println(book); } }}
There is no need to change the BookDeserialiser
as the deserialisation of the author is delegated to the context
. This is another advantage of using the context
to deserialise the other/nested objects. Running the above code will produce the following.
Java Puzzlers: Traps, Pitfalls, and Corner Cases [032133678X]Written by: >> [1] Joshua Bloch >> [2] Neal Gafter
This concludes this section about nested objects. In the next section we will see how to refer to JSON objects located elsewhere in the JSON object tree.
Object References
Consider the following JSON.
{ 'authors': [ { 'id': 1, 'name': 'Joshua Bloch' }, { 'id': 2, 'name': 'Neal Gafter' } ], 'books': [ { 'title': 'Java Puzzlers: Traps, Pitfalls, and Corner Cases', 'isbn': '032133678X', 'authors':[1, 2] }, { 'title': 'Effective Java (2nd Edition)', 'isbn': '0321356683', 'authors':[1] } ]}
The JSON object shown above comprise two authors and two books. The books have a reference to the authors through their ids as in this example the books’ field authors
only contains the authors’ ids. This is quite a common scenario as this approach will reduce the size of the JSON object as duplicate objects are referred to by their id. The following image captures the new JSON object hierarchy.
This resembles relational database tables (Wiki), where the book has a foreign key (Wiki) to the authors table. This new JSON object structure introduces new challenges that need to be addressed. When deserialising the books, we need to get hold of the authors that were deserialised from other branch of the JSON object hierarchy. The book only has the author id. The rest of the author information is found elsewhere, out of scope of the current context.
There are various approaches to this problem, some of which are listed below.
- One approach is to use a two stage processing. First we parse JSON to Java, that is deserialise the books and authors as these appear in JSON. The book class will contain an array of authors’ ids and not an array or authors. Then, in the second stage, we associate the objects and set the authors to their books. The following image shows the flow described here.
This approach requires more classes but provides a great deal of flexibility and provides better separation of concerns. We need to create a set of classes that simply represent the JSON objects in Java, then we need another set of classes that matches our requirements (model). In this example, we have one
Book
class and anotherAuthor
class, two in total. Using this approach, we will end up with four classes, two for the book and another two for the author. While in this example, it may seem feasible, this becomes more complex when we have tens, if not hundreds of classes. - Another approach is to provide the
BookDeserialiser
class with all authors and then have the deserialiser retrieving the authors from this shared object. This approach removes the need of the middle stage as the JSON objects are deserialised into proper Java objects without passing through an intermediate stage.While this approach may sound appealing, it requires that the
BookDeserialiser
and theAuthorDeserialiser
share a common object. Furthermore, when retrieving authors theBookDeserialiser
has to refer to this shared object instead of using theJsonDeserializationContext
as we did before. This approach requires changes in several places. The deserialisers need to be modified and themain()
method too. - The
AuthorDeserialiser
can be made such that it caches the deserialised authors and return these next time requested by their id. This approach is quite attractive as it leverages the use ofJsonDeserializationContext
and make the relationship transparent. Unfortunately it adds in complexity as theAuthorDeserialiser
needs to handle the caching. With that said, this approach requires the least changes as only theAuthorDeserialiser
is modified.As shown in the image above, only the
AuthorDeserialiser
accesses the cache object. The rest of the system is unaware of this.
All approaches are feasible and all have their advantages and disadvantages. We will use the third approach as it is the one that has the least impact on the project.
Observation
In theory, the first approach provides better separation of concerns when compared with the other two. We can have the association logic within the new Data
class. But this requires many changes when compared with the third approach. That was the reason behind the use of the third approach over the other two. Always consider the effort required to make a change and try to minimise it.
The JSON object shown above contains two arrays. We need to have a new Java class that reflects this JSON object.
package com.javacreed.examples.gson.part3;public class Data { private Author[] authors; private Book[] books; // Methods removed for brevity}
The fields order determines which of the two sets is deserialised first. This does not matter in our case and the books can be deserialised before the authors as we will see later on.
The AuthorDeserialiser
need to be modified such that it caches the authors that were deserialised as shown next.
package com.javacreed.examples.gson.part3;import java.lang.reflect.Type;import java.util.HashMap;import java.util.Map;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;import com.google.gson.JsonPrimitive;public class AuthorDeserializer implements JsonDeserializer<Author> { private final ThreadLocal<Map<Integer, Author>> cache = new ThreadLocal<Map<Integer, Author>>() { @Override protected Map<Integer, Author> initialValue() { return new HashMap<>(); } }; @Override public Author deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { // Only the ID is available if (json.isJsonPrimitive()) { final JsonPrimitive primitive = json.getAsJsonPrimitive(); return getOrCreate(primitive.getAsInt()); } // The whole object is available if (json.isJsonObject()) { final JsonObject jsonObject = json.getAsJsonObject(); final Author author = getOrCreate(jsonObject.get("id").getAsInt()); author.setName(jsonObject.get("name").getAsString()); return author; } throw new JsonParseException("Unexpected JSON type: " + json.getClass().getSimpleName()); } private Author getOrCreate(final int id) { Author author = cache.get().get(id); if (author == null) { author = new Author(); author.setId(id); cache.get().put(id, author); } return author; }}
We made several changes in this class. Let us go through these changes one by one.
- The authors are stored in the following object.
private final ThreadLocal<Map<Integer, Author>> cache = new ThreadLocal<Map<Integer, Author>>() { @Override protected Map<Integer, Author> initialValue() { return new HashMap<>(); } };
It uses a
Map<String, Object>
as cache mechanism. The map is saved within aThreadLocal
(JavaDoc) instance to isolate state between the multiple threads. This class allows multiple threads to use the same variable without interfering with the other threads.Observation
Please note that while this approach is thread-safe, it may not fulfil the needs of specific application domain needs and thus one may have to use a different approach. Please refer to the articles: How to Cache Results to Boost Performance and Caching Made Easy with Spring for more caching examples.
- The authors are always retrieved using the following method.
private Author getOrCreate(final int id) { Author author = cache.get().get(id); if (author == null) { author = new Author(); cache.get().put(id, author); } return author; }
This method first tries to obtain the authors instance from the cache. If no author is found with the given id, then one is created and added to the cache.
This approach allows us to create the author with just its id, and then populate its contents later on when they become available. That is why the order of deserialisation does not affect the outcome. We can first deserialise the books and then the authors. In this case, first the authors are created with just their ids and the their names are added.
- The
deserialize()
was modified to handle the new requirements. Since many changes were applied, we will split this method further and describe each part separately.The descerialiser can receive either a
JsonPrimitive
or aJsonObject
. When theBookDeserialiser
executes the following code, theJsonElement
passed to theAuthorDeserialiser
will be an instance ofJsonPrimitive
.// This is executed within the BookDeserialiser Author[] authors = context.deserialize(jsonObject.get("authors"), Author[].class);
The following image shows this process.
The
BookDeserialiser
delegates the deseialisation of theAuthor
s array to the context, and provides an array of integers. For each integer, the context invokes thedeserialize()
method of theAuthorDeserialiser
and pass the integer as aJsonPrimitive
.On the other hand, when the authors are deserialised, we will receive an instance of
JsonObject
containing the author and his/her details. Therefore, before we convert the givenJsonElement
, we need to determine whether it is of the correct type.// Only the ID is available if (json.isJsonPrimitive()) { final JsonPrimitive primitive = json.getAsJsonPrimitive(); final Author author = getOrCreate(primitive.getAsInt()); return author; }
In the above example, only the id was available. The
JsonElement
is converted into aJsonPrimitive
and then into anint
. Thisint
is used to retrieve the author from thegetOrCreate()
method.The
JsonElement
can be of typeJsonObject
as shown next.// The whole object is available if (json.isJsonObject()) { final JsonObject jsonObject = json.getAsJsonObject(); final Author author = getOrCreate(jsonObject.get("id").getAsInt()); author.setName(jsonObject.get("name").getAsString()); return author; }
In this case, the name is added to the
Author
instance returned by thegetOrCreate()
method, before the author is returned.Finally, if the given
JsonElement
instance is neither aJsonPrimitive
nor aJsonObject
, an exception is thrown to indicate that the given type is not supported.throw new JsonParseException("Unexpected JSON type: " + json.getClass().getSimpleName());
The above captures all required changes to accommodate and address the new challenges. The BookDeserialiser
and the main()
method do not require any changes. Running the main()
will produce the following.
Output missing...
This example concludes our article about the Gson desieralizer. Using a custom deserializer is not difficult and enables us to work with different JSON representations with little effort. Note that the Java domain objects do not need to correspond to the JSON object being parsed. Furthermore, we can use existing Java objects with new JSON representations. Some problems may be more challenging than others to solve. Try to keep the changes to the existing code to a minimum and your design as flexible as required (not as possible).
- GSON DESERIALISER EXAMPLE
- Could not find class 'com.google.gson.Gson', referenced from method com.example.test.json.JsonUtil.S
- example
- example
- Example
- example
- Example
- Example
- Example
- Example
- example
- Example
- Example
- Gson
- Gson
- Gson
- Gson
- GSON
- 台湾国立大学机器学习基石.听课笔记(第十三讲):harzard of overfitting
- greenDao_2.0.0学习笔记之1-简介
- hdu 5414 CRB and String
- FastReport实现分组页码(.net版)
- 137Single Number II
- GSON DESERIALISER EXAMPLE
- windows环境下的opencv在qt(msvc)上使用的配置
- Android如何键盘按键响应事件
- osgi6——camel配置学习
- MySql索引算法原理解析(通俗易懂,只讲B-tree)
- Android如何获取手机上面已经安装的app
- poj1797 最短路变形
- 【cJSON】CJSON学习笔记(二)
- java一些重要工具类