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

留言

這個網誌中的熱門文章

最新入門零基礎【從零開始學 Java 程式設計】線上教學課程目錄

NS - Nintendo Switch 遊戲比價網

【從零開始學 Kotlin 程式設計】Android Kotlin 線上教學課程目錄