Android入门——数据解析之使用GSON解析JSON字符串(二)

引言

前一篇文章Android进阶——数据解析之创建及使用JSON字符串(一)总结了关于JSON的基本知识以及原生方式解析JSON(初学者可以先移步去了解下JSON),但原生方式在解析复杂JSON时,就会显得有点麻烦和力不从心,而且不够简捷,于是乎在现实项目中很多开发者都会去采用第三方开源库去解析,比如Google的Gson、阿里的FastJson等等,这篇文章先总结GSON解析。

一、Gson概述

Gson是Google的一个开源Java库,可用于将简单的Java对象POJO(Plain Ordinary Java Object,实际就是普通JavaBeans)转换为JSON字符串形式。同时也可以将JSON字符串转换为等效的POJO。而且Gson可以使用任意Java对象,包括您没有源代码的预先存在的对象,Gson把Json字符串映射成对象时的结构。
这里写图片描述
说到POJO,Gson给了以下的建议和注意事项:

  • 成员变量都应声明为private

  • 尽量不要使用@Expose 注解指明某个字段是否会被序列化或者反序列化,所有包含在当前类(包括父类)中的字段都应该默认被序列化或者反序列化

  • 如果某个字段被 transient 或synthetic修饰,就不会被序列化或者反序列化

  • 当序列化的时候,如果对象的某个字段为null,是不应输出到Json字符串中的;当反序列化的时候,某个字段在Json字符串中找不到对应的值,应该被赋值为null

  • 内部类(或者匿名类anonymous class,或者局部类local class(可以理解为在方法内部声明的类))的某个字段和外部类的某个字段一样的话,就会被忽视,不会被序列化或者反序列化

二、Gson 常用术语

  • Serialization :序列化,使POJO到JSON字符串的过程。

  • Deserialization:反序列化,JSON字符串转换成POJO。

  • JsonElement:该类是一个抽象类,是JsonObject、JsonArray、JsonPrimitive,JsonNull的父类,代表着JSON串的基本元素,该元素可以是一个JsonObject、一个JsonArray、一个Java的基本类型JsonPrimitiveJsonNull等。通过JsonElement提供了一系列的方法来判断当前的JsonElement是否是上述子类的一种,比如isJsonObject()用来判断当前的json元素是否是一个数组类型。

  • JsonPrimitive :对Java的基本类型及其对应的对象类进行了封装,并通过setValue方法为value赋值

  • JSONObject :json对象类,包含了键值对,键是字符串类型,它的值是一个JsonElement。源码中是用 LinkedTreeMap<String, JsonElement> members来保存。

  • JsonArray:JsonElement 的集合。注意数组的元素可以是四种类型中的任意一种,或者混合类型都支持。Json的数组包含的其实也是一个个Json串。在源码中JsonArray中是用一个集合类List来添加json数组中的每个元素。

  • JsonNull:值为null,json中所有的JsonNullObject 调用equals方法判断的话都是相等的

三、Gson 的常用注解

1、 @SerializedName注解

该注解能指定该字段在JSON中对应的字段名称,当你的POJO中的成员变量名和你JSON字符串的键名不同的时候,可以使用该注解来一一映射。@SerializedName注解中可以设置两个属性,value直接设置JSON字符串中要映射的属性名,alternate 设置备选属性,接受一个字符串数组,当由于设计不严谨或者其他原因每个客户端中定义的JSON属性字段名称不一样的时候,就可以使用alternate 增加备选属性,此时无论出现那个value还是alternate 里的属性都能映射成功(当alternate 里定义的属性出现任意一个时均可以得到正确的结果。当多种情况同时出时,以最后一个出现的值为准)

public class User {

  @SerializedName(value = "nl")
  private int age;

  @SerializedName(value = "xm",alternate = {"mz","m"})
  private String name;

}

当使用注解之后这样的字符串{“nl”:1,“xm”:“CrazyMo_”} 可以被被解析到POJO中对应的age和name字段中。

2、@Since和@Until注解

@Since和@Until注解可以在类,字段,以及将来的版本中的“方法”使用来维护同一对象的多个版本@Since表示“从…开始”(即xxx 之后)Until 表示”直到…才”,(即xx 之前)。值得注意的是要让这两个注解生效必须将Gson实例配置为忽略大于某个版本号的任何字段/对象。如果Gson实例上没有设置任何版本,则无论版本如何,它都会对所有字段和类进行序列化和反序列化。

  public class Simple {
	  private String name;
	  @Since(2.0)
	  private int id;//id在版本2.0之后才会序列化
	  @Until(0.5)
	  private String ttx;//,txt在0.5之前才会得到序列化
}

序列化或反序列化带@Since 和@Util 注解的POJO时,需要通过GsonBuilder去设置版本号

VersionedClass versionedObject = new VersionedClass();
Gson gson = new GsonBuilder().setVersion(1.0).create();
String jsonOutput = gson.toJson(someObject);

3、 @Expose注解

该注解能够指定该字段是否能够序列化或者反序列化,默认支持(即为true),Gson建议所有的字段都应该被序列化或者反序列化。如果需要使用得通过GsonBuilder来创建Gson并初始化调用方法builder.excludeFieldsWithoutExposeAnnotation()使之生效

public class Employee {

  @Expose(deserialize = false)
  private String sex;

  @Expose
  private String name;

  @Expose(serialize = false)
  private int age;

  @Expose(serialize = false, deserialize = false)
  private boolean married;

}
//builder.excludeFieldsWithoutExposeAnnotation()方法是该注解生效
final GsonBuilder builder = new GsonBuilder();
builder.excludeFieldsWithoutExposeAnnotation();
final Gson gson = builder.create();

四、使用Gson完成序列化和反序列化

1、引入Gson类库

直接在APP模块下的gradle.build引入

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    compile 'com.google.code.gson:gson:2.8.1'
}

2、使用Gson完成反序列化

使用Gson完成序列化或反序列化很简单,尤其是当POJO的成员变量名和JSON字符串中的键一一对应的时候,只需要构造对象new Gson()并调用toJson方法或者fromJson系方法即可。而当POJO的成员变量名和JSON字符串中的键没有一一对应的时候,我们无论是序列化或是反序列化还需要通过代码设置映射,设置映射的方式有两种:通过 使用@SerializedName注解自定义的类型适配器(TypeAdapter)

gson.toJson(Object);//序列化
gson.fromJson(Reader,Class);//反序列化
gson.fromJson(String,Class);
gson.fromJson(Reader,Type);
gson.fromJson(String,Type);//其中Type可以直接写T.class,
//复杂类型时
Gson gson = new GsonBuilder().serializeNulls().create();
gson.fromJson(pSongs,new TypeToken<SongList>() {}.getType());

需要注意的是“Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2” 原因之一就是因为Gson解析的以"[“开始的Json字符串的时候是会把它解析为数组,如果你把它当成对象的话就会报错,如果你需要解析为对象还需要手动改造源JSON字符串的结构,使其以”{"开头,变成一个完整的对象。

String srcJson="[{"id":"001J5QJL1pRQYB","name":"\u7b49\u4f60\u4e0b\u8bfe(with \u6768\u745e\u4ee3)","artist":["\u5468\u6770\u4f26"],"album":"\u7b49\u4f60\u4e0b\u8bfe","pic_id":"003bSL0v4bpKAx","url_id":"001J5QJL1pRQYB","lyric_id":"001J5QJL1pRQYB","source":"tencent"}]";
srcJson="{\"CrazySongs\""+":"+srcJson+"}";//需要手动改造源json
Gson gson = new GsonBuilder().serializeNulls().create();
SongList sSongList=gson.fromJson(pSongs,new TypeToken<SongList>() {}.getType());//若不手动改造就会报错

2、序列化(POJO——>JSON)自定义类型适配器(TypeAdapter)的使用

实现序列化自定义类型适配器(TypeAdapter)很简单只需要实现JsonSerializer泛型序列化接口即可。

public interface JsonSerializer<T> {
	/**
	 * Gson 会在解析指定类型T数据的时候自动触发当前回调方法进行序列化
	 * @param T 需要转化为Json数据的类型,实现的时候传入要具体的类型
	 * @return 返回T指定的类对应JsonElement
	 */
	public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context);
}

以{“Name”:“JayChou”,“Sex”:“Man”,“Famoussons”:[“开不了口”,“星晴”,“七里香”]}转化为

public class SuperStar {
    private String[] famousSongs;
    private String name;
    private String sex;
    //getter和setter方法等其他方法略
 }

实现自定义序列化类型适配器

    public class StarSerialiser implements JsonSerializer<SuperStar> {

        @Override
        public JsonElement serialize(SuperStar star, Type typeOfSrc, JsonSerializationContext context) {
            final JsonObject jsonObject = new JsonObject();
            jsonObject.addProperty("Name", star.getName());//把SuperStar中的name字段映射到JSON字符串中的Name属性
            jsonObject.addProperty("Sex", star.getSex());//把SuperStar中的sex字段映射到JSON字符串中的Sex属性
            ///jsonObject.add("isbn-13", star.getFamousSongs());

            final JsonArray jsonSongsArray = new JsonArray();
            for (final String song : star.getFamousSongs()) {
                final JsonPrimitive jsonSong = new JsonPrimitive(song);
                jsonSongsArray.add(jsonSong);
            }
            jsonObject.add("Famoussons", jsonSongsArray);//由于famoussons对应的值是数组所以对应的是JsonArray
            return jsonObject;
        }
    }

借助自定义序列化类型适配器完成序列化

    private void parseBean2JSON(){
	    //使用GsonBuilder 创建gson对象
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(SuperStar.class, new StarSerialiser())//注册适配器
                .create();
        SuperStar superStar=new SuperStar(new String[]{"开不了口", "星晴", "七里香"},"JayChou","Man");
        String json = gson.toJson(superStar);
        Log.e("JSON",json.toString());/**06-16 14:36:33.092 16511-16511/com.crazymo.parsejson E/JSON: {"Name":"JayChou","Sex":"Man","Famoussons":["开不了口","星晴","七里香"]}*/
    }
    

3、反序列化(JSON——>POJO)自定义类型适配器(TypeAdapter)的使用

实现反序列化自定义类型适配器(TypeAdapter)很简单只需要实现JsonDeserializer 泛型反序列化接口即可。

public interface JsonDeserializer<T> {
	 public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException;
}

还是以上面的为例,实现反序列化自定义类型适配器

    public class StarDeserializer implements JsonDeserializer<SuperStar>{

        @Override
        public SuperStar deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            JsonObject jsonObject = json.getAsJsonObject();//要转换的JSON字符串最外层是{}对应的是对象将我们的JsonElement转化为JsonObject
            JsonElement jsonName = jsonObject.get("Name");//通过上一步得到JsonObject对象之后就可以通过键得到对应的值
            String name = jsonName.getAsString();//Gson是先通过JSONObject的键获取对应的值,但是此时值的类型是JsonElement的还需要转为String
            String sex = jsonObject.get("Sex").getAsString();
            JsonArray jsonSongsArray = jsonObject.get("Famoussons").getAsJsonArray();
            String[] songs = new String[jsonSongsArray.size()];
            for (int i = 0; i < songs.length; i++) {
                JsonElement jsonAuthor = jsonSongsArray.get(i);
                songs[i] = jsonAuthor.getAsString();
            }

            SuperStar star = new SuperStar(songs,name,sex);
            return star;
        }
    }

测试

    private void parseJSON2Bean(){
        String json="{\"Name\":\"JayChou\",\"Sex\":\"Man\",\"Famoussons\":[\"开不了口\",\"星晴\",\"七里香\"]}";
        GsonBuilder gsonBuilder = new GsonBuilder();//通过GsonBuilder构造gson
        //gsonBuilder.registerTypeAdapter(SuperStar.class, new StarDeserializer());
        gsonBuilder.registerTypeAdapter(new TypeToken<SuperStar>() {}.getType(), new StarDeserializer());
        Gson gson = gsonBuilder.create();
        SuperStar star=gson.fromJson(json,SuperStar.class);
        Log.e("JSON",star.toString());

    }

Gson也支持直接读取本地的JSON文件完成序列化,直接传入一个InputStreamReader对象inReader再调用gson.fromJson(inReader,type)

五、支持空对象null

默认情况下Gson为了实现更紧凑的输出格式而忽略空对象字段,所以必须为这些字段定义一个默认值。要想输出null,就需要对gson进行以下配置

Gson gson = new GsonBuilder().serializeNulls().create();//配置Gson实例以输出null的方式:

以官网上为例

public class Foo {
  private final String s;
  private final int i;

  public Foo() {
    this(null, 5);
  }

  public Foo(String s, int i) {
    this.s = s;
    this.i = i;
  }
}
Gson gson = new GsonBuilder().serializeNulls().create();
Foo foo = new Foo();
String json = gson.toJson(foo);
System.out.println(json);//{"s":null,"i":5}
json = gson.toJson(null);
System.out.println(json);//null

六、使用GsonBuilder导出null值、格式化输出、日期时间

从上面的例子我们知道可以通过GsonBuilder构造gson对象并注册类型适配器来解决POJO与JSON中字段不匹配的问题,其实GsonBuilder还可以帮助我们实现其他一些辅助格式化的工作,可以通过GsonBuilder来间接配置Gson。

Gson gson = new GsonBuilder()
        //序列化null
        .serializeNulls()
        // 设置日期时间格式,另有2个重载方法
        // 在序列化和反序化时均生效
        .setDateFormat("yyyy-MM-dd")
        // 禁此序列化内部类(而非嵌套类)
        .disableInnerClassSerialization()
        //生成不可执行的Json(多了 )]}' 这4个字符)
        .generateNonExecutableJson()
        //禁止转义html标签
        .disableHtmlEscaping()
        //格式化输出
        .setPrettyPrinting()
        .create();

七、GSON使用的典型错误

1、JSONExceptioncom.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Unterminated object at line 1 column 28 path

我在拿到服务器返回给我的JSON字符串时候,发现有些字符串不是严格按照JSON格式进行构造的,导致了在解析时有些结果会导致以上错误,一开始我以为是html标签和null的原因(但其实是返回的JSON格式不规范导致的),于是我按照文档构造了如下的GSON对象

new GsonBuilder()
    .setPrettyPrinting()
    .serializeNulls()
    .disableHtmlEscaping()
    .setLenient()
    .create()
    .fromJson(params, new TypeToken<TTSBean>() {}.getType());

但是依然还是出错,后面排查发现有些结果返回的JSON格式不是很规范,对于{“param1”:您好,我是小U,很高兴为您服务。
}这样形式的json字符串转化没有问题,但是当变成这样{“param1”:您好,我是小U,很 高 兴 为您服务。
}时,值这里多了空白字符时候,转化的时候就会报错,所以导致这个问题排除外特殊html便签之后,再检查下返回的json字符串格式是否正确,个人觉得归根结底是格式出了错误

2、Gson解析遇到key包含空格、中文或者"."等特殊符号

采用加注解的方式来解决,即在相应的字段上使用@SerializedName(“别名”)注解,给Key取别名,其他的写法就和平时一样。

PS:内部类和嵌套类的区别

//静态的内部类称嵌套类(Nested Class)
class Outer {
  class Inner { } //Inner class
  static class Nested { }  //Nested class
}
/*********区别***********/
/*1、内部类的对象隐含一个外部类的对象引用,可以直接引用外部类的方法和属性,另外内部类不能定义静态的属性和*方法,创建内部类对象必须依赖于一个外部对象。
*2、嵌套类不能直接引用外部类的non-satic属性和方法,创建嵌套类对象时不依赖外部类 对象
*********区别***********/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CrazyMo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值