翻译自JavaCreed
首先看一个Json字符串
{
'title': 'Java Puzzlers: Traps, Pitfalls, and Corner Cases',
'isbn-10': '032133678X',
'isbn-13': '978-0321336781',
'authors': ['Joshua Bloch', 'Neal Gafter']
}
如果我们不使用Gson注解@SerializeName
,那怎么来map我们Java bean类中变量的名字
比如我们的java bean类如下
public class Book{
private String[] authors;
#@SerializeName("isbn-10")
#此处我们不使用注解
private String isbn10;
#@SerializeName("isbn-13")
private String isbn13;
private String title;
// Methods removed for brevity
}
在具体讲解Json反序列化之前,先了解一下Gson解析中常用的数据结构。JsonElement,JsonPrimitive,JsonObject,JsonArray,JsonNull
还要注意的是,Gson在解析过程中,是把每一个节点都解析成JsonElement,我们在使用的时候需要通过JsonElement的getAsJsonObject
等方法把它转换成对应的实际类型。
下面展示一下我们自定义的deserializer,
#实现Gson.Deserializer接口,泛型参数是deserializer方法最终要输出的Java Object
public class BookDeserializer implements Gson.Deserializer<Book>{
@Override
public Book deserialize(final JsonElement json,
final Type typeofT,
final JsonDeserializationContext context){
#Gson会先把输入解析成JsonElement,
#由于我们的输入是JsonObject,
#我们需要通过getAsJsonObject进行转换
JsonObject jsonObject = json.getAsJsonObject();
#记住我们得到的都是JsonElement对象,要进行转换
final JsonElement jsonTitle = jsonObject.get("title");
String tilte = jsonTitle.getAsString();
fina JsonArray authorsArray =
jsonObject.get("authors").getAsJsonArray();
final String[] authors = new String[authorsArray.size()];
for(int i = 0;i<authorsArray.size();i++){
authors[i] = authorsArray.get(i).getAsString();
}
final Book book = new Book();
book.setTitle(title);
book.setAuthors(authors);
return book;
}
}
接下来把我们自定义的BookDeserializer注册给Gson
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Book.class, new BookDeserializer());
Gson gson = builder.create();
try(Reader reader = new InputStreamReader(
this.class.getResourceAsStream("books.json","utf-8"))){
Book book = gson.fromJson(reader, Book.class);
}
还考虑上面那个例子,当嵌套的时候如何反序列化
'title': 'Java Puzzlers: Traps, Pitfalls, and Corner Cases',
'isbn-10': '032133678X',
'isbn-13': '978-0321336781',
'authors': [{"id":1,"name":"chico"},{"id":2,"name":"dong"}]
我们这里不采用最简单的使用@SerializeName常规方法,定义一个Author类,然后把Java Bean中的名字和Json中的名字对应起来,所有工作交给Gson自动帮我们做好。
我们仍然要介绍自定义Deserializer的方法,这样可控制性更强,灵活度更高。
首先定义Author类的反序列化器
public class AuthorDeserializer implements JsonDeserialzier{
public Author deserialize(JsonElement element, Type typeOfT,
JsonDeserializerContext context){
JsonObject jsonObject = elemet.getAsJsonObject();
final int id = jsonObject.get("id").getAsInt();
final String name = jsonObject.get("name").getAsString();
Author author = new Author();
author.setId(id);
author.setName(name);
return author;
}
}
然后在BookDeserializer的deseralize方法中,通过第三个参数JonsDeserializerContext来代理反序列化Author。主要就是增加如下代码
Author[] authors = context.deserialize(jsonObject.get("authors"),Author[].class);
然后在使用的时候需要注册这两个反序列化器
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdpater(Book.class,new BookDeserializer());
builder.registerTypeAdapter(Author.class, new AuthorDeserializer());
Gson gson = builder.create();
接下来看一下Gson如何解析外键结构,考虑如下的Json String
"authors":[
{
"id":1,
"name":"chico"
},
{
"id":2,
"name":"dong"
}
],
"books":[
{
"title":"hello",
"isbn":123456,
"authors":[1,2]
}
{
"title":"world",
"isbn":234567,
"authors":[1]
}
]
看到这个Json的结构,books包含了authors的id,类似于数据库的外键,这种结构很常见,因为能够有效的较少传输的数据量。
如何解析这种结构呢,提供几种思路:
1.两段式解析,首先按照Json String的结构,解析出来相应的Java类,这里面就是Book类和Author类,但是Book类此时并不包含Author类的引用,只包含Author的id字段。然后进行转换,把Book类映射到Book2类中,Book2这个类中包含了Author类的引用。这个由于需要中间的转换,不推荐
2.另一个是在BookDeserializer类的deserialise方法中传入Author类的信息,这样在反序列化Book类的时候就可以直接得到相应的Author类对象。这种想法看起来很美好,但是实际上需要很大的架构改动才能实现。首先BookDeserializer和AuthorDeserializer需要共享一个Object,这样AuthorDeserializer才能把自己反序列化的结果通知BookDeserializer。而我们在多个Deserializer中间提供通信的是一个JsonDeserializerContext环境变量,这样的话需要Gson的架构有非常大的改动,不推荐
3.第三种方法是让AuthorDeserializer缓存它的解析结果,并且对外提供通过id寻找缓存的Author的方法。这个方法改动最小,而且对外提供方法用到了JsonDeserializerContext,也非常灵活。
先说一下第一种方法的实现思路
按照Json String定义一个数据结构
public class Data{
Author[] authors;
Book[] books;
//提供一些方法,根据Book中包含的id,找到对应的Author对象
//或者提供一个新类Book2,并提供把Book类转换成Book2的方法
//Book2这个类包含了Author对象的引用
}
重点说一下第三种实现方法,第三种方法也需要一个Data类来对应Json的结构,不同的是不需要提供根据id来查找Author对象的方法,这个功能通过AuthorDeserializer提供。
接下来重写AuthorDeserializer
public class AuthorDeserializer implements JsonDeserializer{
private static final ThreadLocal<Map<Interger,Author>> mCache
= new ThreadLocal(){
protected Map<Integer,Author> initValue(){
return new HashMap();
}
}
public Author deserialse(JsonElement elememt, Type typeOfT,
JsonDeserializerContext context) {
//如果传进来的是id,那么我们直接通过这个Id去寻找对应的Author对象
if(element.isJsonPrimitive()){
final JsonPrimitive primitive = element.getAsJsonPrimitive();
//核心方法,通过id寻找已经缓存的Author对象或者创建Author对应并缓存
return getOrCreate(primitive.getAsInt());
}
//如果传进来的是整个Author的Json String,也去创建或缓存
if(element.isJsonObject){
final JsonObject jsonObject = element.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(int id){
Author author = mCache.get().get(id);
if(author == null){
author = new Author();
author.setId(id);
mCache.get().put(id,author);
}
return author;
}
}
讲一下改动,我们定义了一个ThreadLocal<Map<Integer,Author>>类型的变量作为Cache,这样可以保证每一个线程都有一个独立的Map<Integer,Author>副本.
所有获取Author实例的操作,都由getOrCreate来获得,getOrCreate的巧妙之处在于它仅仅根据id来生成Author,等到真正需要Author的时候,在根据Json String中的内容填入name等字段,这样就不用担心Book和Author哪个先被反序列化了。如果Book先被反序列化,他得到的Author对象是只包含id的Author对象,等Author真正被反序列化的时候剩余的字段会被填上。如果是Author对象先被反序列化,那么直接可以得到完整的Author对象。
然后再看一下deserialize方法的变化,如果我们在BookDeserializer中执行
Author[] authors = context.deserialise(jsonObject.get("authors"),Author[].class);
这样在AuthorDeserializer的deserialize方法中就会走第一个if,也就是
if(element.isJsonPrimitive()){
}
当真正的Author结构的Json String来的时候会走第二个if,也就是
if(element.isJsonObject()){
}
在这里我们根据Json String来补全对应的Author信息。
好了,反序列化最常见的问题已经介绍完了。
网友评论