【Android 入門開發實戰:口罩地圖】JSON 資料解析方式

【Android 入門開發實戰:口罩地圖】線上免費講義課程目錄

上一節,我們透過「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)&nbsp;
        at java.lang.Thread.run(Thread.java:818)&nbsp;

這個時後可以透過 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);
 }

參考資料

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

程式碼範例

範例名稱:使用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
授權範圍:使用時必須註明出處且不得為商業目的之使用
範例下載點:點我下載

這個網誌中的熱門文章

nano 文字編輯器

16天記下7000單字

2023 最新入門零基礎 Kotlin教學【從零開始學 Kotlin 程式設計】Kotlin 教學課程目錄 (Android Kotlin, IntelliJ IDEA, Android Studio, Android APP 開發教學)

Android Studio 歷代版本下載點

2022 最新入門零基礎 Flutter教學 【Flutter 程式設計入門實戰 30 天】Flutter 教學課程目錄 (IntelliJ IDEA 開發教學)