Day 9:JSON 资料解析

本篇文章同步发表在 HKT 线上教室 部落格,线上影音教学课程已上架至 UdemyYoutube 频道。另外,想追踪更多相关技术资讯,欢迎到 脸书粉丝专页 按赞追踪喔~

程序码范例

范例名称:使用 TextView 和 ScrollView 将口罩资料显示在画面上
开发人员:HKT (侯光灿)
程序语言:Kotlin
开发环境:Android Studio 4.1.1 & Android 11 & Kotlin 1.4.21
授权范围:使用时必须注明出处且不得为商业目的之使用
范例下载点:点我下载

范例名称:解析 JSON 资料格式,取出药局名称显示在画面上
开发人员:HKT (侯光灿)
程序语言:Kotlin
开发环境:Android Studio 4.1.1 & Android 11 & Kotlin 1.4.21
授权范围:使用时必须注明出处且不得为商业目的之使用
范例下载点:点我下载

Call API

昨天,我们透过「OkHttp」网路连线方式,抓到网路上口罩资料:

{
  "type": "FeatureCollection",
  "features": [
   ...
   ...
   ...,{
            "type": "Feature",
            "properties": {
                "id": "5901024427",
                "name": "博昱仁爱药局",
                "phone": "(02)87739258",
                "address": "台北市大安区仁爱路4段65号",
                "mask_adult": 0,
                "mask_child": 450,
                "updated": "2020\/09\/13 11:32:37",
                "available": "星期一上午看诊、星期二上午看诊、星期三上午看诊、星期四上午看诊、星期五上午看诊、星期六上午看诊、星期日上午看诊、星期一下午看诊、星期二下午看诊、星期三下午看诊、星期四下午看诊、星期五下午看诊、星期六下午看诊、星期日下午看诊、星期一晚上看诊、星期二晚上看诊、星期三晚上看诊、星期四晚上看诊、星期五晚上看诊、星期六晚上看诊、星期日晚上看诊",
                "note": "周间(周一至周五)上午9点发放号码牌收取健保卡,下午2点领取",
                "custom_note": "",
                "website": "",
                "county": "台北市",
                "town": "大安区",
                "cunli": "仁爱里",
                "service_periods": "NNNNNNNNNNNNNNNNNNNNN"
            },
            "geometry": {
                "type": "Point",
                "coordinates": [
                    121.546869,
                    25.038194
                ]
            }
        },
    ...
    ...
    ...
    
  ]
}

连线到 口罩资料 网址,获取到回应资料,这个动作可以被称爲是「呼叫 API」。APP 手机装置端与远端服务器互相传递资料,我们通常会透过 API (Application Programming Interface:应用程序介面)来沟通。手机获取服务器资料,通常采用 GET 或 POST 方式。

而如果我们 UI 画面不加以处理,直接将资料,透过TextView + ScrollView 显示在画面上:

layout/activity_main.xml

...
...
...

<!-- 在 TextView 外包上一层 ScrollView ,当资料超出画面,可滚动卷轴,看到更多资料内容-->
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/tv_pharmacies_data"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Hello World!" />
    </ScrollView>
...
...
...

MainActivity.kt

//注意要设定 UI ,需要执行在 UiThread 里面,否则会喷错误
runOnUiThread{
    //将 Okhttp 获取到的回应值,指定到画面的 TextView 元件中
     binding.tvPharmaciesData.text= pharmaciesData
}

注意:Kotlin 最新版本 v1.4.2,已经移除废弃的 synthetic 语法,改采用 View Binding,不熟悉的同学,可以参考 KT 专门写的这一篇文章:如何使用元件绑定 View Binding

输出结果

这麽一大包资料,很难找到我们需要的资料。所以我们需要进一步筛选与过滤,取得我们要显示的元素资料。而这次我们的 口罩资料 范例,回应的资料格式是 JSON 资料格式。

JSON 资料格式简介

JSON 资料格式是一种轻量级的资料交换格式,程序很容易建立与解析,人类也易於阅读与书写。JSON 格式存放方式,采物件概念,使用大括号 {} 来包覆,里面资料为采 key 与 value ,中间使用冒号:来分隔。例如:

{
  "name" : "HKT"
}

若有第二个栏位资料,中间使用逗号来区隔是第二个栏位

{
  "name" : "HKT",
  "age": 18
}

常见的 JSON 资料格式,有数字、字串、布林值,而同类型资料,可以使用中括号[]来包覆,每笔资料采用逗号做分隔

{
  "name" : "HKT",
  "age": 18,
  "class":["Java","Kotlin","Dart"]
}

JSON 线上小工具

可以试着将口罩资料转贴到 Online JSON Viewer 的网页右上方的 Text 页签中,完成之後可以按左边的 Viewer 页签,即可透过这套线上 JSON 小工具,快速掌握整个 JSON 资料结构。

解析 JSON 资料格式

解析范例一

解析 JSON 资料格式,注意的是层次概念,以 口罩资料 为例,若我们要取得最外层资料,可以直接获取,例如 「 "type": "FeatureCollection"」,我们解析方式可以写成这样:

//从 Okhttp 收到的回应资料 response,取出 body 的部分。
//注意这里,response 不能二次使用,不然会喷错误。
//所以我们将他转存到 pharmaciesData 。
val pharmaciesData = response.body?.string()

//将 pharmaciesData 整包字串资料,转成 JSONObject 格式
val obj = JSONObject(pharmaciesData)

//这个时候,我们就可以透过 getString 的方式,里面放 key (name) 值,
//即可以获取到最外层的 type 栏位资料值。
Log.d("HKT",obj.getString("type"))

输出结果

FeatureCollection

解析范例二

如果我们要获取的是 features 里面的 properties 里面的 name。解析 JSON 资料,除了要注意层次外,还要注意结构。features 是一个阵列 [] ,中括号来包覆资料,就需要将他转换成 JSONArray。

val pharmaciesData = response.body?.string()

//将 pharmaciesData 整包字串资料,转成 JSONObject 格式
val obj = JSONObject(pharmaciesData)

//features 是一个阵列 [] ,需要将他转换成 JSONArray
val featuresArray = JSONArray(obj.getString("features"))

//透过 for 回圈,即可以取出所有的药局名称
for (i in 0 until featuresArray.length()) {
    val properties = featuresArray.getJSONObject(i).getString("properties")
    val property = JSONObject(properties)
    Log.d("HKT", "name: ${property.getString("name")}")
}

输出结果

name: 中美药局
name: 新东洋药局
name: 辰好药局
name: 杏安药局
name: 明皇药局
name: 全国大药局
name: 政德药局
name: 嘉方药局
name: 庆丰综合药局
...
...
...

如果我们 UI 画面不加以处理,直接将资料,透过TextView + ScrollView 显示在画面上:

MainActivity.kt

//药局名称变数宣告
var propertiesName: String = ""

//透过 for 回圈,即可以取出所有的药局名称
for (i in 0 until featuresArray.length()) {
    val properties = featuresArray.getJSONObject(i).getString("properties")
    val propertieObj = JSONObject(properties)

    //将每次获取到的药局名称,多加跳行符号,存到变数中
    propertiesName+= propertiesName + propertieObj.getString("name") +"\n"
}
//最後取得所有药局名称资料,指定显示到 TextView 元件中
tv_pharmacies_data.text = propertiesName

使用 String 处理串接文字,当资料很多时很容易造成 OOM 记忆体不足,建议换成 StringBuilder

val pharmaciesData = response.body?.string()

//将 pharmaciesData 整包字串资料,转成 JSONObject 格式
val obj = JSONObject(pharmaciesData)

//features 是一个阵列 [] ,需要将他转换成 JSONArray
val featuresArray = JSONArray(obj.getString("features"))


//药局名称变数宣告
//                var propertiesName: String = ""
val propertiesName = StringBuilder()
//透过 for 回圈,即可以取出所有的药局名称
for (i in 0 until featuresArray.length()) {
    val properties = featuresArray.getJSONObject(i).getString("properties")
    val propertieObj = JSONObject(properties)

    //将每次获取到的药局名称,多加跳行符号,存到变数中
//                    propertiesName += propertiesName + propertieObj.getString("name") + "\n"
    propertiesName.append(propertieObj.getString("name") + "\n")
}

runOnUiThread {
    //最後取得所有药局名称资料,指定显示到 TextView 元件中
    binding.tvPharmaciesData.text = propertiesName
}

输出结果

解析范例三

解析 JSON 资料,使用 getString 时,若资料中没有对应的 key (name)值,会发生例外状况(Exception)。

val obj = JSONObject(pharmaciesData)

//资料没有 typeeeee 这个 key(name)值,直接获会喷错误
Log.d("HKT",obj.getString("typeeeee"))

输出结果

E: FATAL EXCEPTION: OkHttp Dispatcher
    Process: com.thishkt.pharmacies, PID: 14914
    java.lang.Error: org.json.JSONException: No value for typeeeee
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1121)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:590)
        at java.lang.Thread.run(Thread.java:818)
     Caused by: org.json.JSONException: No value for typeeeee
        at org.json.JSONObject.get(JSONObject.java:389)
        at org.json.JSONObject.getString(JSONObject.java:550)
        at com.thishkt.pharmacies.MainActivity$getPharmaciesData$1.onResponse(MainActivity.kt:59)
        at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1115)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:590) 
        at java.lang.Thread.run(Thread.java:818) 

这个时後可以透过 has 或 isNull 方法来避免例外错误:

//方法一:使用 has 判断是否存在这个资料,存在时才获取资料
if(obj.has("typeeeee")){
    Log.d("HKT",obj.getString("typeeeee"))
}else{
    Log.d("HKT","has 判断没有这个资料")
}

//方法二:使用 isNull 判断是为空,不为空才获取资料
if(!obj.isNull("typeeeee")){
    Log.d("HKT",obj.getString("typeeeee"))
}else{
    Log.d("HKT","isNull 判断,没有这个资料")
}

但例外有时真的出乎意料,所以在解析资料时,为了避免不可预期错误造成 APP 闪退,会多加 try..catch 来防止,如:

try {
     JSONObject result = new JSONObject();
     ...
 } catch (e: JSONException) {
     throw new RuntimeException(e);
 }

参考资料

HKT 线上教室
https://tw-hkt.blogspot.com/

Freepik
https://www.freepik.com/

JSONObject
https://developer.android.com/reference/org/json/JSONObject

JSONArray
https://developer.android.com/reference/org/json/JSONArray

JSONException
https://developer.android.com/reference/org/json/JSONException?hl=en


那今天【iThome 铁人赛】就介绍到这边罗~

顺带一提,KT 线上教室,脸书粉丝团,会不定期发布相关资讯,不想错过最新资讯,不要忘记来按赞,追踪喔!也欢迎大家将这篇文章分享给更多人喔。

我们明天再见罗!!!掰掰~


<<:  Day-9 Excel筛选大秘辛

>>:  Day 09 Azure Storage Account- 给照片找个家

[经典回顾]知名通讯软件过度存取用户资讯事件

老议题+最近新闻 厂商的说法 iOS系统为App开发者提供相簿更新通知标准能力,相簿发生内容更新时会...

Day21 ( 高级 ) 心电感应 ( 广播 )

心电感应 ( 广播 ) 教学原文参考:心电感应 ( 广播 ) 这篇文章会使用「按钮依序开关灯」的范例...

2.4.8 Design System - Icon

因为疫情的关系 体会到很多事情都要持之以恒 在突然被打乱的生活节奏中 要怎麽找回另一个习惯的步调 ...

用这9种技巧让你的部落格有个好名字

一、前言 经营部落格,会是一个长期的网路事业,在初期有许多部分我认为就应该思考好,在整个经营路上才...

[Day 9] SRE - 自动化

自动化 什麽东西可以自动化?对我而言只要可以列出SOP的事情,都可以实现自动化。 价值 一致性 当不...