早些时候,我开发天气模块的时候,服务器来的数据比较简单,我就直接通过JSONObject
解析了。到了后来,增加了新闻模块,那使用JSONObject
显然就不能满足需求了,所以我选择了Gson
。在手机App开发上,这些东西都已经用烂了,但是在车联网上,这东西还不怎么用,因为车联网也就是最近2年才火起来的。
我用到的大都是Serialization(序列化)的操作,反序列化(Deserialization)的操作不多,所以我主要说一说序列化操作。
注解的使用:
public @interface Expose
用来指示该类的 JSON应该是序列化或者反序列。使用该注解,除非使用 GsonBuilder
生成 Gson
并调用 GsonBuilder. excludeFieldsWithoutExposeAnnotation ()
方法, 否则此注释无效。例如:
public class User {
@Expose
private String firstName;
@Expose(serialize = false)
private String lastName;
@Expose (serialize = false, deserialize = false)
private String emailAddress;
private String password;
}
如果通过Gson gson =new Gson();
gson.toJson(); gson.fromJson();
的方式来解析Json,那么上面那四个字段都会进行序列化或反序列化。如果通过
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
的方式的话解析Json的话,那么将会排除password字段,因为该字段没有标记@Expose;
。lastName和emailAddress也不能被序列化,同时emailAddress也不可以被反序列化。
public @interface JsonAdapter
在字段或者类上面添加的注解,用于指定对应TypeAdapter
或者TypeAdapterFactory
,或者实现一个或两个 JsonDeserializer
与 JsonSerializer
的类。
加了这个注解之后,Gson就会自动调用它来序列化或者反序列化。
比如用户在实现了TypeAdapter
之后,需要调用GsonBuilder gsonBuilder = new GsonBuilder();gsonBuilder .registerTypeAdapter();
,来解析Json。那么如果加了这个注解,就不需要配置GsonBuilder
了,因为注解的优先级高。例如:
//类的注解
@JsonAdapter(UserJsonAdapter.class)
public class User {
public final String firstName, lastName;
private User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
public class UserJsonAdapter extends TypeAdapter<User> {
...省略代码...
}
//字段的注解
private static final class Gadget {
@JsonAdapter(UserJsonAdapter2.class)
final User user;
Gadget(User user) {
this.user = user;
}
}
public @interface SerializedName
这个注解是最最好用的一个。该注解能指定该字段在JSON中对应的字段名称。
举个例子,比如服务端给你返回了一个这样的字段。
{
“num”: “2343567”,
“title”: “Java”
}
我不喜欢这个title字段,这个看起来有些抽象,怎样简单的定义这个字段,而且还可以定制自己喜欢的单词。对,就是这个注解可以帮你。比如我们可以定义:
@SerializedName(“title”)
private String name;
在定义@SerializedName的时候有两个key可以供我们选择。
一个是value
:当它被序列化或反序列化时所需要的字段名。
另一个是alternate
:在反序列化时字段的其他名称。
完整的例子:
public class MyClass {
@SerializedName("name")
String a;
@SerializedName(value="name1", alternate={"name2", "name3"})
String b;
String c;
public MyClass(String a, String b, String c) {
this.a = a;
this.b = b;
this.c = c;
}
}
MyClass target = new MyClass("v1", "v2", "v3");
Gson gson = new Gson();
String json = gson.toJson(target);
System.out.println(json);
===== OUTPUT =====
{"name":"v1","name1":"v2","c":"v3"}
当反序列化时,注释中指定的所有值将被反序列化到字段中。例如:
MyClass target = gson.fromJson("{'name1':'v1'}", MyClass.class);
assertEquals("v1", target.b);
target = gson.fromJson("{'name2':'v2'}", MyClass.class);
assertEquals("v2", target.b);
target = gson.fromJson("{'name3':'v3'}", MyClass.class);
assertEquals("v3", target.b);
关于assertEquals,断言的意思。我们在写Android测试用例的时候,会使用这个方法。判断用例执行完成后得到的值是否与预期值一致。比如Assert.assertEquals(1,2);那么系统会报异常junit.framework.AssertionFailedError: expected:<1> but was:<2>。
public @interface Since
这个注解的意思是:在…版本之后无效(不包括当前版本)
当定义了Gson gson = new GsonBuilder().setVersion(1.0).create()
之后:
public class User {
private String firstName;
private String lastName;
@Since(0.8)
private String emailAddress;
@Since(1.1)
private String password;
}
这时使用Gson解析的结果将会排除password字段。
public @interface Until
该注解的意思是:在…版本之前无效(包括当前版本)。
当定义了Gson gson = new GsonBuilder().setVersion(1.2).create()
之后
public class User {
private String firstName;
private String lastName;
@Until(1.1)
private String emailAddress;
@Until(1.1)
private String password;
}
这时使用Gson解析的结果将会排除emailAddress与password字段。
通常我们在解析Json的时候,会使用两种方式。一种是默认的fromJson
另一种是实现TypeAdapter
。
先贴一个简单的Json文件,然后使用默认的解析方式来完成:
先定义JavaBean:
public class Message {
@SerializedName("id")
private long _id;
@SerializedName("text")
private String text;
@SerializedName("geo")
private List<Double> doubles =new ArrayList<>();
@SerializedName("user")
private User user;
}
然后直接解析
Gson gson =new Gson();
Message msg = gson.fromJson(Json,Message.class);
下面这个Json稍微比上一个复杂一点。这次我们使用TypeAdapter。
public class ParseJsonAdapter2 extends TypeAdapter<List<Message>>{
private List<Message> readMessagesArray(JsonReader jsonReader) throws IOException {
List<Message> messages = new ArrayList<>();
jsonReader.beginArray();
while (jsonReader.hasNext()) {
messages.add(readMessage(jsonReader));
}
jsonReader.endArray();
return messages;
}
private Message readMessage(JsonReader jsonReader) throws IOException {
long id = -1;
String text = null;
User user = null;
List<Double>geo =null;
jsonReader.beginObject();
while(jsonReader.hasNext()){
String name = jsonReader.nextName();
if(name.equals("id")){
id = jsonReader.nextLong();
}else if (name.equals("text")){
text = jsonReader.nextString();
}else if (name.equals("geo") && jsonReader.peek() != JsonToken.NULL) {
geo = readDoublesArray(jsonReader);
}else if (name.equals("user")) {
user = readUser(jsonReader);
}else{
jsonReader.skipValue();
}
}
jsonReader.endObject();
return new Message(id, text, user, geo);
}
private User readUser(JsonReader jsonReader) throws IOException{
String username = null;
int followersCount = -1;
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (name.equals("name")) {
username = jsonReader.nextString();
} else if (name.equals("followers_count")) {
followersCount = jsonReader.nextInt();
} else {
jsonReader.skipValue();
}
}
jsonReader.endObject();
return new User(username, followersCount);
}
private List<Double> readDoublesArray(JsonReader jsonReader) throws IOException{
List<Double> doubles = new ArrayList<Double>();
jsonReader.beginArray();
while (jsonReader.hasNext()) {
doubles.add(jsonReader.nextDouble());
}
jsonReader.endArray();
return doubles;
}
@Override
public List<Message> read(JsonReader arg0) throws IOException {
return readMessagesArray(arg0);
}
@Override
public void write(JsonWriter arg0, List<Message> arg1) throws IOException {
// TODO Auto-generated method stub
}
}
创建Gson,直接解析:
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Message.class, new ParseJsonAdapter2());
final Gson gson = gsonBuilder.create();
Type type = new TypeToken<Message>(){}.getType();
List<Message>messages = gson.fromJson(Json_B,type);
使用TypeAdapter
是使用流的方式来进行序列或反序列化的,所以是很高效的一种解析方式。那么解析流程是怎么样的呢:
- 首先根据你的Json 文本的结构创建方法。
- 调用
beginArray ()
以读取数组的左方括号。然后创建一个 while 循环, 当hasNext ()
为 false 时终止。最后, 通过调用endArray ()
来读取数组的右方括号。 - 在对象处理方法中, 首先调用
beginObject ()
读取对象的左大括号。然后创建一个 while 循环, 并根据其名称为局部变量赋值。当hasNext ()
为 false 时, 此循环应终止。最后, 通过调用endObject ()
来读取对象的右大括号。 - 当遇到嵌套对象或数组时, 委托给相应的方法去处理。
- 如果值可能为 null, 则应首先使用
peek ()
进行检查。也可以使用nextNull ()
或skipValue ()
来赋值Null。
看一下,fromJson与registerTypeAdapter方法在入口处做了什么:
//fromJson
public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
Object object = fromJson(json, (Type) classOfT);
return Primitives.wrap(classOfT).cast(object);
}
//registerTypeAdapter
public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
//...省略代码
if (typeAdapter instanceof TypeAdapter<?>) {
factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter));
}
return this;
}
首先我们注册了TypeAdapter以后,要注意后面两个参数,不要和使用fromJson传入的类型冲突了,保证type类型一致,返回的结果一致。
TypeToken:它是Gson提供的数据类型转换器,可以支持各种数据集合类型转换。
关于JsonSerializer
如果你不满意Gson默认的序列化,你可以自己实现这个接口,去做序列化的内容。
最后记得注册 GsonBuilder.registerTypeAdapter(Type, Object)
。
如果需要了解一个库怎么用,最好的办法不是百度,而是去看GitHub上面的相关文档。
我看到有几个工程师每次改二行代码就提交一次,查看提交记录,里面一望无际的都是他的名字。这样看着觉得工作好TM认真啊!哎,谁让人家是Android Engineer 呢!?