Android下的Json学习指南

Json基础知识

  • Json是什么

    Json是JavaScript Object Notation(JavaScript对象表示法)的缩写,是一种轻量级的文本数据交换格式,是用来存储和交换文本信息的语法。

    它独立于语言且具有自我描述性,更容易理解。虽然使用Javascript语法来描述数据对象,但很多编程语言都支持Json。

    Json的文件类型是".json",MIME类型是"application/json"。

  • Json的语法

    典型的Json实例:

{ "photos": { "page": 1, "pages": 10, "perpage": 100, "total": "1000", 
    "photo": [
      { "id": "31624984467"}, {"owner": "155140314@N03"}, {"secret": "efa8606dc9"}]}}

Json语法是JS语法的子集:

  1. 数据在名称/值对{ key:value }中
  2. 数据由逗号分隔
  3. 大括号保存对象
  4. 中括号保存数组

Json的key为字符串,值可以是:

  1. 数字(整数或浮点数)
  2. 字符串(在双引号中)
  3. 逻辑值(true 或 false)
  4. 数组(在中括号中)
  5. 对象(在大括号中)
  6. null
  • Json对象

    JSON 对象在大括号({ })中书写,对象可以包含多个名称/值对。名称/值对中使用冒号(:)分割,每个名称/值对对使用逗号(,)分割。

    一个典型的例子:
    {"id": "31624984467"}

  • Json数组

    JSON 数组在中括号中书写,数组可包含多个对象,也可只是合法的基本数据类型:

    [{ "id": "31624984467"},{ "owner": "155140314@N03"}]

    ["a","b","c"]

Android中的Json解析

利用Android自带的Json解析类

  • 使用JSONObject,JSONArray来解析:

    JSONObject类:生成Json对象,可以完成Json字符串与Java对象的相互转换。

    JSONArray类:生成Json数组,可以完成Json字符串与Java集合或对象的相互转换。

    JSONStringer类:Json文本构建类,这个类可以帮助快速和便捷的创建JSON text, 每个JSONStringer实体只能对应创建一个JSON text。

    JSONException类:Json异常。

    使用步骤介绍:

    1. 获取Json字符串。一般是从网络获取,为了方便这里直接从本地获取。
    2. 使用JSONObject类,利用Json字符串生成JSONObject对象。
    3. 使用getJSONObject,getJSONArray等方法一个个的将Json字符串分解,再从中抠出所需要的属性。

举个栗子:

josn数据为:

{ "photos": { "page": 1, "pages": 10, "perpage": 100, "total": "1000",
    "photo": [
{ "id": "31624984467", "owner": "155140314@N03","title": "Happy New Year 2019 : Happy New Year Happy New Year 2019", "url_s": "https:\/\/farm5.staticflickr.com\/4852\/31624984467_efa8606dc9_m.jpg"},
{ "id": "31624992207", "owner": "163032290@N04","title": "","url_s": "https:\/\/farm5.staticflickr.com\/4844\/31624992207_a3196f29b6_m.jpg"},
{ "id": "31624994507", "owner": "146047562@N03","title": "swt33.c33.kr정품비아그라구입방법카톡:swt33텔레:swt33비아그라구입처,비아그라구입정보,클럽비아그라판매처,비아그라구입사이트,강력비아그라효능","url_s": "https:\/\/farm5.staticflickr.com\/4819\/31624994507_0a022a924c_m.jpg"}]}

从资源目录中获取json数据

private String jsonFromLocal() throws IOException {

    InputStream inStr = getResources().openRawResource(R.raw.jsontest);
    InputStreamReader inReader = new InputStreamReader(inStr);
    BufferedReader bufferedReader = new BufferedReader(inReader);
    StringBuilder builder = new StringBuilder(" ");
    String str = null;
    while ((str = bufferedReader.readLine()) != null) {
        builder.append(str);
        builder.append("\n");
    }
    inReader.close();
    bufferedReader.close();
    return builder.toString();
}

接着就开始扣需要的属性了。

//生成JSONObject对象
String json = jsonFromLocal();
JSONObject jsonBody = new JSONObject(json);
parseItem(items, jsonBody);`

private void parseItem(List<Item> items, JSONObject jsonBody) throws JSONException {//先获取最外层的对象photos
    JSONObject photosJsonObject = jsonBody.getJSONObject("photos");
//再获取photos内的对象数组photo
    JSONArray photoJsonArray = photosJsonObject.getJSONArray("photo");
    for (int i = 0; i < 3; i++) {
//再依据具体的key来获取需要的属性
        JSONObject photoJsonObject = photoJsonArray.getJSONObject(i);
        Item item = new Item();
        item.setId(photoJsonObject.getString("id"));
        item.setCaption(photoJsonObject.getString("title"));
        item.setUrl(photoJsonObject.getString("url_s"));
        item.setOwner(photoJsonObject.getString("owner"));
        items.add(item);
    }
}

item类:

public class Item {
private String id;
private String url;
private String owner;
private String caption;
......(get,set等方法)
  • 使用JsonReader来解析

    这种方式有点类似XML文档的解析,使用深度优先遍历,寻找到需要的标记然后消耗掉它。对象的解析从beginObject()开始,到endObject()结束。数组的解析从beginArray()开始,到endArray()结束。

    下面我们来看一看吧,json数据依旧是之前的!

        JsonReader reader = new JsonReader(new StringReader(json));//获取reader对象
        try {
            reader.beginObject();//从Json流中消耗一个标记,开始解析对象
            while (reader.hasNext()) {//循环判断当前数组或者对象是否有下一个元素
                String propertyName = reader.nextName();//获取key值
                if (propertyName.equals("photos")) {
                    readPhotos(reader, items);
                } else {
                    reader.skipValue();//跳过下一个不需要的元素
                }
            }
            reader.endObject();//结束对象解析
        } finally {
            reader.close();
        }
    }
    
    private void readPhotos(JsonReader reader, List<Item> items) throws IOException {
        reader.beginObject();
        while (reader.hasNext()) {
            String propertyName = reader.nextName();
            if (propertyName.equals("photo")) {
               readPhoto(reader, items);
            } else {
                reader.skipValue();
            }
        }
        reader.endObject();
    }
    
    private void readPhoto(JsonReader reader, List<Item> items)  throws IOException {
        reader.beginArray();//开始数组解析
        String id = null,url_s = null,owner = null,title = null;
        while (reader.hasNext()) {
            PhotoItem item = new PhotoItem();
            reader.beginObject();
            while (reader.hasNext()) {
                String propertyName = reader.nextName();
                switch (propertyName) {
                    case "id":
                        id = reader.nextString();//获取"id"的值,返回String类型
                        break;
                    case "url_s":
                        url_s = reader.nextString();
                        break;
                    case "owner":
                        owner = reader.nextString();
                        break;
                    case "title":
                        title = reader.nextString();
                        break;
                    default:
                        reader.skipValue();
                        break;
                }
            }
                item.setId(id);
                item.setUrl(url_s);
                item.setOwner(owner);
                item.setCaption(title);
            items.add(item);
            reader.endObject();
        }
        reader.endArray();//结束数组解析
    }`
    
    

注意:
如果使用了nextName(),而没有使用next **()( **可以是String,Int,Long等)方法或者skipValue(),这时候就会触发异常,这一组方法需要配套使用。同理使用了beginObject()就要使用endObject()结尾,使用了beginArray()就要使用endArray()结尾。

开源库Gson使用

尝试完了繁琐的Android自带的Json解析类,现在就来看看第三方开源库Gson的威力吧!

  • 导入依赖

dependencies { implementation 'com.google.code.gson:gson:2.8.5' }

  • Gson的基本使用

    Gson对象的创建

    有两种方式来创建Gson对象。

    第一种是直接new一个对象: Gson gson = new Gson();

    第二种是利用GsonBuilder来创建,可以进行多项配置:Gson gson = new GsonBuilder().create();

    Gson的反序列化

    Gson最为重要的两个方法分别是toJson()和fromJson()。toJson()用来序列化,fromJson()用来反序列化。

    1 简单Json数组解析:

    Json数据:

[ "Happy New Year 2019 : Happy New Year Happy New Year 2019",
  "Karma's black iron prison",
  "Hong Kong gov't bid to redevelop Fanling course 'absurd' says golf alliance convener"
]

Json数组转化为字符串数组:

       Gson gson = new Gson();
        String json_t2 = null;
        try {
            json_t2 = jsonFromLocal(R.raw.json_t2);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String[] strings = gson.fromJson(json_t2, String[].class);
        for (String st : list) {
            Log.i(TAG, st);
        }

Json数组转化为字符串List:

         Gson gson = new Gson();
        String json_t2 = null;
        try {
            json_t1 = jsonFromLocal(R.raw.json_t2);
        } catch (IOException e) {
            e.printStackTrace();
        }
        List<String> list = gson.fromJson(json_t2,new TypeToken<List<String>>(){}.getType());
        for (String st : list) {
            Log.i(TAG, st);
        }

Gson中的泛型表示:

在解析Json数组时,一般使用两种方法,一个是数组,一个是List。而因为List对于增删操作比较方便,一般选用List。
但对于List来说,上面代码中的 String[].class ,不能直接改为List<String>.class。因为对于Java来说 List<String>List<Item>这两个字节码文件只有一个,就是 List.class,这就是Java泛型使用时要注意的泛型擦除问题。

为了解决上述问题,Gson提供TypeToken来实现对泛型的支持。TypeToken 这个类帮助我们捕获(capture)像 List 这样的泛型信息。Java编译器会把捕获到的泛型信息编译到这个匿名内部类里,然后在运行时就可以被getType()方法用反射的 API 提取到。也就是将泛型T转成 .class

2 稍复杂Json数据解析:

Json数据,目的是为了拿到totol数据:

{
  "photo": [
    {
      "page": 1,
      "pages": 10,
      "perpage": 100,
      "total": "1000"
    },
    {
      "page": 2,
      "pages": 20,
      "perpage": 200,
      "total": "1000"
    },
    {
      "page": 3,
      "pages": 30,
      "perpage": 300,
      "total": "1000"
    }
  ]
}

第一步:根据Json数据建立简单Java类。可以使用Android Studio平台中的GsonFormat插件来快速构建。

 public class JsonT1 {

    public List<PhotoBean> photo;

    public static class PhotoBean {
        /**
         * page : 1
         * pages : 10
         * perpage : 100
         * total : 1000
         */

        public int page;
        public int pages;
        public int perpage;
        public String total;
    }
}

第二步:解析Json数据。这里有两种方式,第一种是利用JsonParse类解析,有点类似于Android自带的JsonObject和JsonArray解析方式,得要扣需要的key。下面来看一下实现代码。

JsonObject jsonObject = new JsonParser().parse(json_t1).getAsJsonObject();
        JsonArray jsonArray = jsonObject.getAsJsonArray("photo");
        List<JsonT1.PhotoBean> jsonT1List = new ArrayList<>();
        for (JsonElement element : jsonArray) {
            JsonT1.PhotoBean jsonT1 = gson.fromJson(element, new TypeToken<JsonT1.PhotoBean>() {
            }.getType());
            jsonT1List.add(jsonT1);
        }
    Log.i(TAG, jsonT1List.get(0).total);

第二种是直接将Json数据解析为类对象,在得到对象之后再从对象内取出需要的List。

JsonT1 t1 = gson.fromJson(json_t1, JsonT1.class);
        List<JsonT1.PhotoBean> photoBeanList = t1.photo;
        Log.i(TAG,photoBeanList.get(0).total);

比较而言第二种方式更简洁一点,而且遇到更复杂一点的Json数据也可以使用。

3复杂Json数据解析

当遇到这种Json数据时:

{
  "photos": {
    "page": 1,
    "pages": 10,
    "perpage": 100,
    "total": "1000",
    "photo": [
      {
        "id": "31624984467",
        "owner": "155140314@N03",
        "secret": "efa8606dc9",
        "server": "4852",
        "farm": 5,
        "title": "Happy New Year 2019 : Happy New Year Happy New Year 2019",
        "ispublic": 1,
        "isfriend": 0,
        "isfamily": 0,
        "url_s": "https:\/\/farm5.staticflickr.com\/4852\/31624984467_efa8606dc9_m.jpg",
        "height_s": "240",
        "width_s": "240"
      },
      {
        "id": "31624992207",
        "owner": "163032290@N04",
        "secret": "a3196f29b6",
        "server": "4844",
        "farm": 5,
        "title": "",
        "ispublic": 1,
        "isfriend": 0,
        "isfamily": 0,
        "url_s": "https:\/\/farm5.staticflickr.com\/4844\/31624992207_a3196f29b6_m.jpg",
        "height_s": "135",
        "width_s": "240"
      },
  "stat": "ok"
}

就到了JsonReader大显身手的地方了。使用这种流式处理方式再复杂的数据也能搞定。JsonReader是不是有点眼熟?没错,它和之前提到的Android自带的JsonReader处理方式几乎没有区别,连名字都一样。

JsonReader.png
  • Gson注解

    Gson的常用注解有@Expose@SerializedName@Since@Until@JsonAdapter

    1 @Expose注解

    本注解用于指定某个属性是否进行序列化或反序列化,搭配GsonBuilder使用,源码如下。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Expose {
  public boolean serialize() default true;
  public boolean deserialize() default true;
}

包含两个属性serializedeserializeserialize 用于指定是否进行序列化,deserialize用于指定是否进行反序列化,默认为true。

举个栗子:

@Expose(serialize = false, deserialize = true)//可以反序列化,不可以序列化
 public int page;

使用:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

2 @SerializedName注解

重命名指定的属性,源码如下。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SerializedName {
  String value();
}

使用:

@SerializedName("pagesTotal")
 public int pages;

3 @Since@Until注解

@Expose注解一样,用来指定某一属性是否可以序列化或反序列化。@Since@Until 都包含一个 Double 属性值,用于设置版本号。Since 的意思是“自……开始”,Until 的意思是“到……为止”。当版本( GsonBuilder 设置的版本) 大于或等于 Since 属性值或小于 Until 属性值时字段会进行序列化和反序列化操作,而没有声明的字段都会加入序列化和反序列操作。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Until {
  double value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Since {
 double value();
}

使用:

@Since(1.1)
 public int perpage;
@Until(1.8)
 public String total;
Gson gson = new GsonBuilder().setVersion(1.3).create();

4 @JsonAdapter注解

自定义序列化和反序列化,可以修饰类和字段。

首先需要自定义一个TypeAdapter的子类来接管目标类的序列化和反序列化过程,并且实现write和read方法。

public class SelfTypeAdapter extends TypeAdapter<JsonT1> {
    @Override
    public void write(JsonWriter out, JsonT1 value) throws IOException {

    }

    @Override
    public JsonT1 read(JsonReader in) throws IOException {
        JsonT1 t1 = new JsonT1();
        in.beginObject();
        //.....解析操作
        in.endObject();
        return t1;
    }
}

接着是注册自定义的SelfTypeAdapter类:

Gson gs = new GsonBuilder().registerTypeAdapter(JsonT1.class, new SelfTypeAdapter()).create();
  • 其他Gson配置

1 格式化输出

Gson gson = new GsonBuilder()
                .setPrettyPrinting()
                .create();

2 日期时间格式化输出

Gson gson = new GsonBuilder()
                .setPrettyPrinting()
                .setDateFormat("yyyy-MM-dd HH:mm:ss:SSS")//日期时间格式

3 Null值输出

Gson gson = new GsonBuilder()
                .serializeNulls() 
                .create();

参考:
https://juejin.im/post/59e5663f51882546b15b92f0
https://www.jianshu.com/p/0444693c2639
https://www.jianshu.com/p/886f7b7fca7d
http://www.runoob.com/json/js-json-arrays.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 1.概述2.Gson的目标3.Gson的性能和扩展性4.Gson的使用者5.如何使用Gson 通过Maven来使用...
    人失格阅读 14,196评论 2 18
  • 为了更好的学习Gson,特将Gson User Guide翻译如下。由于本人英文水平有限,如有错误,还请指正,谢谢...
    WeberLisper阅读 6,706评论 0 6
  • JSON的特点: 1、JSON比XML的数据传递的有效性高;2、JSON完全独立于编程语言;3、JSON的本质是具...
    程序员之路阅读 3,476评论 0 3
  • 概况 Gson是一个Java库,它可以用来把Java对象转换为JSON表达式,也可以反过来把JSON字符串转换成与...
    木豚阅读 6,757评论 0 2
  • 城市的喧嚣,校园的浪漫,市井的嘈杂,街头的繁华,似乎在我眼里都没有那么如愿的美好和怡然。因为身边没有你,所以...
    焉然南山阅读 146评论 0 1