彩云天气JSON数据解析
JSON數據解析——彩云天氣api
彩云天氣API
首先在彩云天氣官網注冊一個賬號,注冊地址是:
https://dashboard.caiyunapp.com/
注冊之后可查看API文檔
天氣app所需數據
1、地區數據
訪問地址接口可查詢到全球絕大多數地區的數據信息
https://api.caiyunapp.com/v2/place?query=北京&token={token}&lang=zh_CN
query參數指定的是要查詢的關鍵字(如地名),token傳入剛剛申請到的令牌值。服務器會返回我們一段JSON格式的數據,我們所需獲取的數據有name(該地區的名字)、location(該地區的經緯度)、formatted_address(該地區的地址)
{"status":"ok","query":"北京","places":[{"name":"北京南站","formatted_address":"中國 北京市 豐臺區 永外大街車站路12號","location":{"lat":39.865246,"lng":116.378517}},{"name":"北京西站","formatted_address":"中國 北京市 豐臺區 蓮花池東路118號","location":{"lat":39.89491,"lng":116.322056}},{"name":"北京站","formatted_address":"中國 北京市 東城區 毛家灣胡同甲13號","location":{"lat":39.902842,"lng":116.427341}},{"name":"北京北站","formatted_address":"中國 北京市 西城區 北濱河路1號","location":{"lat":39.944876,"lng":116.353063}},{"name":"北京東站(地鐵站)","formatted_address":"中國 北京市 朝陽區 (在建)28號線","location":{"lat":39.902267,"lng":116.482682}}]
}
其展示效果如下:
2、實時天氣數據
實時天氣信息API接口:
https://api.caiyunapp.com/v2.5/{token}/101.6656,39.2072/realtime
token仍是剛剛傳入的令牌值,101.6656,39.2072分別是維度和經度,中間用逗號隔開,這樣服務器就會把該地區的實時天氣信息以JSON格式返回給我們,我們從中提取需要的數據,realtime中包含的就是當前地區的實時天氣信息,其中temperature表示當前的溫度,skycon表示當前的天氣情況,air_quality中包含一些空氣質量的數據,這里使用aqi的值作為空氣質量指數顯示在界面上
{"status":"ok","result":{"realtime":{"temperature":17.0,"skycon":"PARTLY_CLOUDY_DAY","air_quality":{"aqi":{"chn":78}}}}
}
其展示效果如下:
3、未來幾天的天氣數據
未來幾天的天氣信息API接口
https://api.caiyunapp.com/v2.5/{token}/116.378517,39.865246/daily.json
這個接口返回的數據也比較復雜,我們依舊只需提取需要的數據
daily包含的就是當前地區未來幾天的天氣信息,temperature表示未來幾天的溫度值,skycon表示未來幾天的天氣情況,life_index中包含一些生活指數,coldRish表示感冒指數,CarWashing表示洗車指數,ultraviolet表示紫外線指數,dressing表示穿衣指數
{“status:" "ok","result": {"daily": {"temperature": [ {"max":18.0,"min":9.0},...],"skycon":[{"date":"2022-03-28T00:00+08:00","value":"PARTLY_CLOUDY_DAY"},...]"life_index":{"coldRisk":[{"desc":"極易發"},...],"carWashing"[{"desc":"較不適宜"},...],"ultraviolet":[{"desc":"強"},...],"dressing":[{"desc:"冷""},...]}}}
}
其展示效果如下:
使用retrofit請求api獲取數據
以上述中地區部分為例
{"status":"ok","query":"北京","places":[{"name":"北京南站","formatted_address":"中國 北京市 豐臺區 永外大街車站路12號","location":{"lat":39.865246,"lng":116.378517}},{"name":"北京西站","formatted_address":"中國 北京市 豐臺區 蓮花池東路118號","location":{"lat":39.89491,"lng":116.322056}},{"name":"北京站","formatted_address":"中國 北京市 東城區 毛家灣胡同甲13號","location":{"lat":39.902842,"lng":116.427341}},{"name":"北京北站","formatted_address":"中國 北京市 西城區 北濱河路1號","location":{"lat":39.944876,"lng":116.353063}},{"name":"北京東站(地鐵站)","formatted_address":"中國 北京市 朝陽區 (在建)28號線","location":{"lat":39.902267,"lng":116.482682}}]
}
分析這段json數據:
- 第一層是一個花括號,即jsonObject對象,其中有status、query屬性以及一個places的JSON數組(中括號為JSONArray數組)
- 第二層places的JSON數組,其中有name、formatted_address、location
- 第三層location有lat和lng兩個屬性
kotlin代碼實現請求數據
我們在定義這一部分數據模型時,對每一層都需要有一個數據類,按照以上分析的JSON格式來定義
新建一個PlaceResponse.kt文件,并在這個文件中編寫如下代碼
/*** 第一層:status和places的JSON數組*/
data class PlaceResponse(val status: String, val places: List<Place>)
/*** 第二層:name、location、formatted_address* 由于JSON中的一些字段命名可能與kotlin的命名規范不一致,所以使用了@SerializedName注解* @ SerializedName注解使JSON字段和kotlin字段之間建立映射關系*/
data class Place(val name: String, val location: Location,@SerializedName("formatted_address") val address: String)
/*** 第三層:lng、lat*/
data class Location(val lng: String, val lat: String)
定義好數據模型之后,我們可以開始編寫網絡層相關的代碼了。首先定義一個用于訪問彩云天氣城市搜索API的Retrofit接口
還記得上面那個測試地區JSON數據的API接口嗎?就是使用剛剛的接口,不過我們需要向其中傳入我們的“query”和“token”以便可以通過搜索框查到大部分地區的數據
interface PlaceService {/*** 當調用searchPlaces時,Retrofit就會自動發起一個GET請求,去訪問GET注解中配置的地址* 其中token和lang參數都是不變的,可以直接固定寫在注解中* query參數是需要動態指定的,這里使用@Query注解的方式來實現* * 另外searchPlaces的返回值被聲明成Call<PlaceResponse>,這樣JSON數據就會自動解析成PlaceResponse對象*/@GET("v2/place?token=${SunnyWeatherApplication.TOKEN}&lang=zh_CN")fun searchPlaces(@Query("query") query: String) : Call<PlaceResponse>
}
現在,我們就可以開始測試進行連接通信了
新建一個Test測試類,寫上主函數進行測試,需要注意的是,kotlin的主函數需要在上方加上@JvmStatic注解
class Test {companion object{//BASE_URL不會變,直接傳入我們所需的彩云天氣的URL用于指定Retrofit的根路徑private const val BASE_URL = "https://api.caiyunapp.com/"//main函數入口@JvmStaticfun main(args: Array<String>) {//從控制臺輸入要查詢的地區名稱val placeStr = readLine()//獲取PlaceService接口的動態代理對象val retrofit = Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).build()//創建API接口對象val placeService = retrofit.create(PlaceService::class.java)//創建一個請求對象val call: Call<PlaceResponse> = placeService.searchPlaces(placeStr!!)//開始進行連接請求call.enqueue(object : Callback<PlaceResponse> {override fun onResponse(call: Call<PlaceResponse>,response: Response<PlaceResponse>) {val placeResponse = response.body();if (placeResponse?.status == "ok"){val places = response.body()?.placesfor (place in places!!){val name = place.nameval address = place.addressval location = place.locationval lng = location.lngval lat = location.latprintln("地名:${name}, 地址:${address}, 經緯度(${lng},${lat})")}}}override fun onFailure(call: Call<PlaceResponse>, t: Throwable) {TODO("Not yet implemented")println("error")}})}}
}
以下就是服務器返回的數據被自動解析成JSON對象的結果
我們對數據進行進一步提取后就完成我們本次的網絡請求了
上面Test類只是進行一個簡單測試,在實際項目(以《第一行代碼》彩云天氣開發為實例進行學習)中我們不可能每次都去寫一個單獨的對象去獲取Service接口
因此在項目中,為了更好的使用Service接口,Retrofit構建器一般會使用以下寫法
新建一個ServiceCreator 單例類
object ServiceCreator {//BASE_URL不會變,直接傳入我們所需的彩云天氣的URL用于指定Retrofit的根路徑private const val BASE_URL = "https://api.caiyunapp.com/"private val retrofit = Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).build()/*** 提供一個外部可見的create方法并接收一個class類型參數* 這樣經過封裝之后,通過參數的不同可以創建相應的Service接口,而不用為每一個類都單獨寫一個構造器*/fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass)inline fun <reified T> create(): T = create(T::class.java)
}
接下來需要定義一個統一的網絡數據源訪問入口,對所有的網絡請求的API進行封裝。
object SunnyWeatherNetwork {//使用ServiceCreator創建一個placeService接口的動態代理對象private val placeService = ServiceCreator.create(PlaceService::class.java)/*** 當外部調用SunnyWeatherNetwork的searchPlaces時,retrofit會立即發出網絡請求* 同時當前的協程也會被阻塞住,知道服務器響應我們的請求之后* await()函數會將解析出來的數據模型對象取出并返回*///定義searchPlaces函數并調用searchPlaces()方法以發起搜索城市數據請求suspend fun searchPlaces(query: String) = placeService.searchPlaces(query).await()private suspend fun <T> Call<T>.await(): T {//suspend掛起函數關鍵字//await()是一個掛起函數,給它聲明一個泛型T,并將await()函數定義成call<T>的擴展函數return suspendCoroutine { continuation ->enqueue(object : Callback<T> {//直接調用enqueue()方法讓Retrofit發起網絡請求override fun onResponse(call: Call<T>, response: Response<T>) {val body = response.body()if (body != null) continuation.resume(body)else continuation.resumeWithException(RuntimeException("response body is null"))}override fun onFailure(call: Call<T>, t: Throwable) {continuation.resumeWithException(t)}})}}
}
這樣每次需要使用某個API接口時,只需要在SunnyWeatherNetwork 中創建相關的接口對象傳入對應的類型參數就可以獲取到了
那么現在我們對獲取地區數據進行測試,其實只需要通過SunnyWeatherNetwork.searchPlaces()就能得到地區數據了
因為searchPlaces()被設置為suspend掛起,因此給剛剛的main()加上一個runBlocking(調用了 runblocking 的線程會阻塞直到內部的協程執行完畢)這樣就可以執行了
companion object{@JvmStaticfun main(args: Array<String>) = runBlocking{//從控制臺輸入要查詢的地區名稱val placeStr = readLine()/*** 在SunnyWeatherNetwork中已經創建了placeService接口的動態代理對象* 若要使用別的接口,也只需要在SunnyWeatherNetwork添加方法即可*/val placeResponse = SunnyWeatherNetwork.searchPlaces(placeStr!!)if (placeResponse.status == "ok"){val places = placeResponse.placesfor (place in places){val name = place.nameval address = place.addressval location = place.locationval lng = location.lngval lat = location.latprintln("地名:${name}, 地址:${address}, 經緯度(${lng},${lat})")}}}
}
其實現在真正發送請求只需要一行代碼:
val placeResponse = SunnyWeatherNetwork.searchPlaces(placeStr!!)
而且現在調用其他的API接口都可以按照這種模式進行編寫
總結
以上是生活随笔為你收集整理的彩云天气JSON数据解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Maven,Jetty和Tomcat
- 下一篇: PS无法拖拽置入图片解决办法