Saya mencoba mengambil respons JSON dan menyimpan hasilnya dalam variabel. Saya memiliki versi kode ini yang berfungsi di rilis Swift sebelumnya, hingga versi GM dari Xcode 8 dirilis. Saya telah melihat beberapa posting serupa di StackOverflow: Swift 2 Parsing JSON - Tidak dapat menggunakan nilai tipe 'AnyObject' dan JSON Parsing di Swift 3 .
Namun, tampaknya gagasan yang disampaikan di sana tidak berlaku untuk skenario ini.
Bagaimana cara mengurai respons JSON dengan benar di Swift 3? Apakah ada yang berubah dalam cara JSON dibaca di Swift 3?
Di bawah ini adalah kode yang dimaksud (dapat dijalankan di taman bermain):
import Cocoa
let url = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
if let url = NSURL(string: url) {
if let data = try? Data(contentsOf: url as URL) {
do {
let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments)
//Store response in NSDictionary for easy access
let dict = parsedData as? NSDictionary
let currentConditions = "\(dict!["currently"]!)"
//This produces an error, Type 'Any' has no subscript members
let currentTemperatureF = ("\(dict!["currently"]!["temperature"]!!)" as NSString).doubleValue
//Display all current conditions from API
print(currentConditions)
//Output the current temperature in Fahrenheit
print(currentTemperatureF)
}
//else throw an error detailing what went wrong
catch let error as NSError {
print("Details of JSON parsing error:\n \(error)")
}
}
}
Edit: Berikut adalah contoh hasil dari panggilan API setelahnyaprint(currentConditions)
["icon": partly-cloudy-night, "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precipIntensity": 0, "windSpeed": 6.04, "summary": Partly Cloudy, "ozone": 321.13, "temperature": 49.45, "dewPoint": 41.75, "apparentTemperature": 47, "windBearing": 332, "cloudCover": 0.28, "time": 1480846460]
Jawaban:
Pertama-tama jangan pernah memuat data secara sinkron dari URL jarak jauh , gunakan selalu metode asinkron seperti
URLSession
.terjadi karena kompilator tidak tahu jenis objek perantara (misalnya
currently
dalam["currently"]!["temperature"]
) dan karena Anda menggunakan tipe kumpulan Foundation sepertiNSDictionary
kompilator tidak tahu sama sekali tentang tipe tersebut.Selain itu di Swift 3, Anda perlu memberi tahu kompiler tentang tipe semua objek yang disubscribe.
Anda harus mentransmisikan hasil serialisasi JSON ke tipe sebenarnya.
Kode ini menggunakan
URLSession
dan tipe asli Swift secara eksklusifUntuk mencetak semua pasangan kunci / nilai
currentConditions
Anda dapat menulisCatatan tentang
jsonObject(with data
:Banyak (sepertinya semua) tutorial menyarankan
.mutableContainers
atau.mutableLeaves
opsi yang benar-benar tidak masuk akal di Swift. Kedua opsi tersebut adalah opsi Objective-C warisan untuk menetapkan hasil keNSMutable...
objek. Di Swift,var
iable apa pun dapat berubah secara default dan meneruskan salah satu opsi tersebut dan menetapkan hasilnya kelet
konstanta tidak berpengaruh sama sekali. Lebih jauh lagi, sebagian besar implementasi tidak pernah memutasi JSON yang telah dideserialisasi.Satu-satunya (jarang) pilihan yang berguna di Swift adalah
.allowFragments
yang diperlukan jika jika JSON akar objek bisa menjadi jenis nilai (String
,Number
,Bool
ataunull
) bukan salah satu jenis koleksi (array
ataudictionary
). Tetapi biasanya menghilangkanoptions
parameter yang berarti Tidak ada opsi .================================================== =========================
Beberapa pertimbangan umum untuk mengurai JSON
JSON adalah format teks yang tersusun rapi. Sangat mudah untuk membaca string JSON. Baca string dengan cermat . Hanya ada enam tipe berbeda - dua tipe koleksi dan empat tipe nilai.
Jenis koleksinya adalah
[]
- Swift:[Any]
tetapi dalam banyak kasus[[String:Any]]
{}
- Swift:[String:Any]
Jenis nilainya adalah
"Foo"
, genap"123"
atau"false"
- Swift:String
123
atau123.0
- Swift:Int
atauDouble
true
ataufalse
tidak dalam tanda kutip ganda - Swift:true
ataufalse
null
- Swift:NSNull
Menurut spesifikasi JSON, semua kunci dalam kamus harus ada
String
.Pada dasarnya, selalu disarankan untuk menggunakan binding opsional untuk membuka bungkus opsional dengan aman
Jika objek root adalah dictionary (
{}
), gunakan tipe tersebut[String:Any]
dan mengambil nilai dengan kunci dengan (
OneOfSupportedJSONTypes
baik koleksi JSON atau tipe nilai seperti yang dijelaskan di atas.)Jika objek root adalah array (
[]
) gunakan tipe[[String:Any]]
dan melakukan iterasi melalui array dengan
Jika Anda membutuhkan item pada indeks tertentu, periksa juga apakah indeks tersebut ada
Dalam kasus yang jarang terjadi, JSON hanyalah salah satu jenis nilai - bukan jenis koleksi - Anda harus meneruskan
.allowFragments
opsi dan mentransmisikan hasilnya ke jenis nilai yang sesuai, misalnyaApple telah menerbitkan artikel komprehensif di Swift Blog: Working with JSON in Swift
================================================== =========================
Di Swift 4+,
Codable
protokol menyediakan cara yang lebih mudah untuk mengurai JSON langsung ke dalam struct / kelas.Misalnya contoh JSON yang diberikan dalam pertanyaan (sedikit diubah)
dapat diterjemahkan ke dalam struct
Weather
. Jenis Swift sama seperti yang dijelaskan di atas. Ada beberapa opsi tambahan:URL
dapat diterjemahkan secara langsung sebagaiURL
.time
integer dapat diterjemahkan sebagaiDate
dengandateDecodingStrategy
.secondsSince1970
.keyDecodingStrategy
.convertFromSnakeCase
Sumber Berkode lainnya:
sumber
dict!["currently"]!
kamus agar compiler dapat menyimpulkan langganan kunci berikutnya dengan aman.Perubahan besar yang terjadi dengan Xcode 8 Beta 6 untuk Swift 3 adalah bahwa id sekarang mengimpor
Any
daripadaAnyObject
.Ini berarti yang
parsedData
dikembalikan sebagai kamus kemungkinan besar dengan tipe[Any:Any]
. Tanpa menggunakan debugger saya tidak dapat memberi tahu Anda dengan tepat apa yangNSDictionary
akan dilakukan cast Anda, tetapi kesalahan yang Anda lihat adalah karenadict!["currently"]!
memiliki tipeAny
Jadi, bagaimana Anda mengatasi ini? Dari cara Anda mereferensikannya, saya berasumsi bahwa itu
dict!["currently"]!
adalah kamus sehingga Anda memiliki banyak pilihan:Pertama, Anda dapat melakukan sesuatu seperti ini:
Ini akan memberi Anda objek kamus yang kemudian dapat Anda kueri nilai-nilainya sehingga Anda bisa mendapatkan suhu seperti ini:
Atau jika Anda lebih suka, Anda dapat melakukannya sejalan:
Semoga ini membantu, saya khawatir saya belum sempat menulis aplikasi sampel untuk mengujinya.
Satu catatan terakhir: hal termudah untuk dilakukan, mungkin cukup dengan memasukkan muatan JSON ke dalamnya
[String: AnyObject]
tepat di awal.sumber
dict!["currently"]! as! [String: String]
akan crash[String: String]
tidak bisa bekerja sama sekali karena ada beberapa nilai numerik. Tidak berfungsi di Mac Playgroundsumber
Saya membuat quicktype persis untuk tujuan ini. Cukup tempelkan sampel JSON Anda dan quicktype menghasilkan hierarki jenis ini untuk data API Anda:
Ini juga menghasilkan kode marshaling bebas ketergantungan untuk membujuk nilai kembalian
JSONSerialization.jsonObject
menjadi aForecast
, termasuk konstruktor praktis yang mengambil string JSON sehingga Anda dapat dengan cepat mengurai nilai yang sangat diketikForecast
dan mengakses bidangnya:Anda dapat menginstal quicktype dari npm dengan
npm i -g quicktype
atau menggunakan UI web untuk mendapatkan kode lengkap yang dibuat untuk ditempelkan ke taman bermain Anda.sumber
Diperbarui yang
isConnectToNetwork-Function
setelah itu, berkat ini posting .Saya menulis metode tambahan untuk itu:
Jadi sekarang Anda dapat dengan mudah memanggil ini di aplikasi Anda di mana pun Anda mau
sumber
Swift memiliki inferensi tipe yang kuat. Mari singkirkan boilerplate "if let" atau "guard let" dan paksa pembongkaran menggunakan pendekatan fungsional:
Hanya satu baris kode dan tidak ada kekuatan yang dibuka atau casting tipe manual. Kode ini berfungsi di taman bermain, jadi Anda dapat menyalin dan memeriksanya. Berikut adalah implementasi di GitHub.
sumber
Ini adalah cara lain untuk menyelesaikan masalah Anda. Jadi silakan lihat solusi di bawah ini. Semoga bisa membantu Anda.
sumber
Masalahnya ada pada metode interaksi API. Penguraian JSON hanya diubah dalam sintaks. Masalah utamanya adalah dengan cara mengambil data. Apa yang Anda gunakan adalah cara sinkron untuk mendapatkan data. Ini tidak berhasil di setiap kasus. Yang harus Anda gunakan adalah cara asinkron untuk mengambil data. Dengan cara ini, Anda harus meminta data melalui API dan menunggunya merespons dengan data. Anda dapat mencapai ini dengan sesi URL dan perpustakaan pihak ketiga seperti
Alamofire
. Di bawah ini adalah kode untuk metode Sesi URL.sumber
Jika saya membuat model menggunakan json sebelumnya Menggunakan tautan ini [blog]: http://www.jsoncafe.com untuk menghasilkan struktur yang Dapat Dikodekan atau Format Apa Pun
Model
Parse
sumber