Ini JSON saya
{
"id": 1,
"user": {
"user_name": "Tester",
"real_info": {
"full_name":"Jon Doe"
}
},
"reviews_count": [
{
"count": 4
}
]
}
Ini adalah struktur yang saya inginkan untuk disimpan (tidak lengkap)
struct ServerResponse: Decodable {
var id: String
var username: String
var fullName: String
var reviewCount: Int
enum CodingKeys: String, CodingKey {
case id,
// How do i get nested values?
}
}
Saya telah melihat Dokumentasi Apple tentang decoding struct bersarang, tetapi saya masih tidak mengerti bagaimana melakukan berbagai level JSON dengan benar. Bantuan apa pun akan sangat dihargai.
Encodable
untukServerResponse
struktur mengikuti pendekatan yang sama. Apakah itu mungkin?ServerResponse
memiliki lebih sedikit data daripadaRawServerResponse
. Anda dapat merekamRawServerResponse
instance, mengupdatenya dengan properti dariServerResponse
, lalu menghasilkan JSON darinya . Anda bisa mendapatkan bantuan yang lebih baik dengan memposting pertanyaan baru tentang masalah khusus yang Anda hadapi.Untuk memecahkan masalah Anda, Anda dapat membagi
RawServerResponse
implementasi Anda menjadi beberapa bagian logika (menggunakan Swift 5).# 1. Menerapkan properti dan kunci pengkodean yang diperlukan
import Foundation struct RawServerResponse { enum RootKeys: String, CodingKey { case id, user, reviewCount = "reviews_count" } enum UserKeys: String, CodingKey { case userName = "user_name", realInfo = "real_info" } enum RealInfoKeys: String, CodingKey { case fullName = "full_name" } enum ReviewCountKeys: String, CodingKey { case count } let id: Int let userName: String let fullName: String let reviewCount: Int }
# 2. Tetapkan strategi decoding untuk
id
propertiextension RawServerResponse: Decodable { init(from decoder: Decoder) throws { // id let container = try decoder.container(keyedBy: RootKeys.self) id = try container.decode(Int.self, forKey: .id) /* ... */ } }
# 3. Tetapkan strategi decoding untuk
userName
propertiextension RawServerResponse: Decodable { init(from decoder: Decoder) throws { /* ... */ // userName let userContainer = try container.nestedContainer(keyedBy: UserKeys.self, forKey: .user) userName = try userContainer.decode(String.self, forKey: .userName) /* ... */ } }
# 4. Tetapkan strategi decoding untuk
fullName
propertiextension RawServerResponse: Decodable { init(from decoder: Decoder) throws { /* ... */ // fullName let realInfoKeysContainer = try userContainer.nestedContainer(keyedBy: RealInfoKeys.self, forKey: .realInfo) fullName = try realInfoKeysContainer.decode(String.self, forKey: .fullName) /* ... */ } }
# 5. Tetapkan strategi decoding untuk
reviewCount
propertiextension RawServerResponse: Decodable { init(from decoder: Decoder) throws { /* ...*/ // reviewCount var reviewUnkeyedContainer = try container.nestedUnkeyedContainer(forKey: .reviewCount) var reviewCountArray = [Int]() while !reviewUnkeyedContainer.isAtEnd { let reviewCountContainer = try reviewUnkeyedContainer.nestedContainer(keyedBy: ReviewCountKeys.self) reviewCountArray.append(try reviewCountContainer.decode(Int.self, forKey: .count)) } guard let reviewCount = reviewCountArray.first else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath + [RootKeys.reviewCount], debugDescription: "reviews_count cannot be empty")) } self.reviewCount = reviewCount } }
Implementasi lengkap
import Foundation struct RawServerResponse { enum RootKeys: String, CodingKey { case id, user, reviewCount = "reviews_count" } enum UserKeys: String, CodingKey { case userName = "user_name", realInfo = "real_info" } enum RealInfoKeys: String, CodingKey { case fullName = "full_name" } enum ReviewCountKeys: String, CodingKey { case count } let id: Int let userName: String let fullName: String let reviewCount: Int }
extension RawServerResponse: Decodable { init(from decoder: Decoder) throws { // id let container = try decoder.container(keyedBy: RootKeys.self) id = try container.decode(Int.self, forKey: .id) // userName let userContainer = try container.nestedContainer(keyedBy: UserKeys.self, forKey: .user) userName = try userContainer.decode(String.self, forKey: .userName) // fullName let realInfoKeysContainer = try userContainer.nestedContainer(keyedBy: RealInfoKeys.self, forKey: .realInfo) fullName = try realInfoKeysContainer.decode(String.self, forKey: .fullName) // reviewCount var reviewUnkeyedContainer = try container.nestedUnkeyedContainer(forKey: .reviewCount) var reviewCountArray = [Int]() while !reviewUnkeyedContainer.isAtEnd { let reviewCountContainer = try reviewUnkeyedContainer.nestedContainer(keyedBy: ReviewCountKeys.self) reviewCountArray.append(try reviewCountContainer.decode(Int.self, forKey: .count)) } guard let reviewCount = reviewCountArray.first else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath + [RootKeys.reviewCount], debugDescription: "reviews_count cannot be empty")) } self.reviewCount = reviewCount } }
Pemakaian
let jsonString = """ { "id": 1, "user": { "user_name": "Tester", "real_info": { "full_name":"Jon Doe" } }, "reviews_count": [ { "count": 4 } ] } """ let jsonData = jsonString.data(using: .utf8)! let decoder = JSONDecoder() let serverResponse = try! decoder.decode(RawServerResponse.self, from: jsonData) dump(serverResponse) /* prints: ▿ RawServerResponse #1 in __lldb_expr_389 - id: 1 - user: "Tester" - fullName: "Jon Doe" - reviewCount: 4 */
sumber
struct
Anda yang digunakanenum
dengan kunci. yang jauh lebih elegan 👍Daripada memiliki satu
CodingKeys
enumerasi besar dengan semua kunci yang Anda perlukan untuk mendekode JSON, saya sarankan untuk memisahkan kunci untuk setiap objek JSON bersarang Anda, menggunakan enumerasi bersarang untuk mempertahankan hierarki:// top-level JSON object keys private enum CodingKeys : String, CodingKey { // using camelCase case names, with snake_case raw values where necessary. // the raw values are what's used as the actual keys for the JSON object, // and default to the case name unless otherwise specified. case id, user, reviewsCount = "reviews_count" // "user" JSON object keys enum User : String, CodingKey { case username = "user_name", realInfo = "real_info" // "real_info" JSON object keys enum RealInfo : String, CodingKey { case fullName = "full_name" } } // nested JSON objects in "reviews" keys enum ReviewsCount : String, CodingKey { case count } }
Ini akan memudahkan untuk melacak kunci di setiap level di JSON Anda.
Sekarang, ingatlah bahwa:
Sebuah kontainer mengetik digunakan untuk memecahkan kode objek JSON, dan diterjemahkan dengan
CodingKey
jenis sesuai (seperti yang kita telah didefinisikan di atas).Sebuah wadah unkeyed digunakan untuk memecahkan kode array JSON, dan diterjemahkan secara berurutan (yaitu setiap kali Anda memanggil decode atau metode kontainer bersarang di atasnya, maju ke elemen berikutnya dalam array). Lihat bagian kedua dari jawaban untuk bagaimana Anda bisa mengulang satu.
Setelah mendapatkan kunci tingkat atas Anda penampung dari dekoder dengan
container(keyedBy:)
(karena Anda memiliki objek JSON di tingkat atas), Anda dapat berulang kali menggunakan metode:nestedContainer(keyedBy:forKey:)
untuk mendapatkan objek bertingkat dari objek untuk kunci tertentunestedUnkeyedContainer(forKey:)
untuk mendapatkan larik bersarang dari objek untuk kunci tertentunestedContainer(keyedBy:)
untuk mendapatkan objek bersarang berikutnya dari lariknestedUnkeyedContainer()
untuk mendapatkan larik bersarang berikutnya dari larikSebagai contoh:
struct ServerResponse : Decodable { var id: Int, username: String, fullName: String, reviewCount: Int private enum CodingKeys : String, CodingKey { /* see above definition in answer */ } init(from decoder: Decoder) throws { // top-level container let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(Int.self, forKey: .id) // container for { "user_name": "Tester", "real_info": { "full_name": "Jon Doe" } } let userContainer = try container.nestedContainer(keyedBy: CodingKeys.User.self, forKey: .user) self.username = try userContainer.decode(String.self, forKey: .username) // container for { "full_name": "Jon Doe" } let realInfoContainer = try userContainer.nestedContainer(keyedBy: CodingKeys.User.RealInfo.self, forKey: .realInfo) self.fullName = try realInfoContainer.decode(String.self, forKey: .fullName) // container for [{ "count": 4 }] – must be a var, as calling a nested container // method on it advances it to the next element. var reviewCountContainer = try container.nestedUnkeyedContainer(forKey: .reviewsCount) // container for { "count" : 4 } // (note that we're only considering the first element of the array) let firstReviewCountContainer = try reviewCountContainer.nestedContainer(keyedBy: CodingKeys.ReviewsCount.self) self.reviewCount = try firstReviewCountContainer.decode(Int.self, forKey: .count) } }
Contoh decoding:
let jsonData = """ { "id": 1, "user": { "user_name": "Tester", "real_info": { "full_name":"Jon Doe" } }, "reviews_count": [ { "count": 4 } ] } """.data(using: .utf8)! do { let response = try JSONDecoder().decode(ServerResponse.self, from: jsonData) print(response) } catch { print(error) } // ServerResponse(id: 1, username: "Tester", fullName: "Jon Doe", reviewCount: 4)
Iterasi melalui wadah tanpa kunci
Mempertimbangkan kasus di mana Anda ingin
reviewCount
menjadi[Int]
, di mana setiap elemen mewakili nilai untuk"count"
kunci di JSON bertingkat:"reviews_count": [ { "count": 4 }, { "count": 5 } ]
Anda harus melakukan iterasi melalui penampung tanpa kunci bersarang, mendapatkan penampung kunci bersarang di setiap iterasi, dan mendekode nilai untuk
"count"
kunci tersebut. Anda dapat menggunakancount
properti dari kontainer tanpa kunci untuk mengalokasikan lebih dulu array yang dihasilkan, dan kemudianisAtEnd
properti untuk melakukan iterasi melaluinya.Sebagai contoh:
struct ServerResponse : Decodable { var id: Int var username: String var fullName: String var reviewCounts = [Int]() // ... init(from decoder: Decoder) throws { // ... // container for [{ "count": 4 }, { "count": 5 }] var reviewCountContainer = try container.nestedUnkeyedContainer(forKey: .reviewsCount) // pre-allocate the reviewCounts array if we can if let count = reviewCountContainer.count { self.reviewCounts.reserveCapacity(count) } // iterate through each of the nested keyed containers, getting the // value for the "count" key, and appending to the array. while !reviewCountContainer.isAtEnd { // container for a single nested object in the array, e.g { "count": 4 } let nestedReviewCountContainer = try reviewCountContainer.nestedContainer( keyedBy: CodingKeys.ReviewsCount.self) self.reviewCounts.append( try nestedReviewCountContainer.decode(Int.self, forKey: .count) ) } } }
sumber
I would advise splitting the keys for each of your nested JSON objects up into multiple nested enumerations, thereby making it easier to keep track of the keys at each level in your JSON
?CodingKeys
enum besar dengan semua kunci yang Anda perlukan untuk memecahkan kode objek JSON Anda, Anda harus membaginya menjadi beberapa enum untuk setiap objek JSON - misalnya, dalam kode di atas yang kita milikiCodingKeys.User
dengan kunci untuk memecahkan kode objek JSON pengguna ({ "user_name": "Tester", "real_info": { "full_name": "Jon Doe" } }
), jadi hanya kunci untuk"user_name"
&"real_info"
.reviews_count
yang merupakan array kamus. Saat ini, kodenya berfungsi seperti yang diharapkan. ReviewsCount saya hanya memiliki satu nilai dalam array. Tetapi bagaimana jika saya benar-benar menginginkan array review_count, maka saya hanya perlu mendeklarasikanvar reviewCount: Int
sebagai array, kan? ->var reviewCount: [Int]
. Dan kemudian saya juga perlu mengeditReviewsCount
enum, kan?Int
, tetapi larik objek JSON yang masing-masing memilikiInt
nilai untuk kunci tertentu - jadi yang perlu Anda lakukan adalah mengulanginya wadah yang tidak dikunci dan dapatkan semua wadah bertingkat yang dikunci, mendekodeInt
untuk masing-masing wadah (dan kemudian menambahkannya ke larik Anda), misalnya gist.github.com/hamishknight/9b5c202fe6d8289ee2cb9403876a1b41Banyak jawaban bagus telah diposting, tetapi ada metode yang lebih sederhana yang belum dijelaskan IMO.
Saat nama kolom JSON ditulis menggunakan,
snake_case_notation
Anda masih dapat menggunakancamelCaseNotation
di file Swift Anda.Anda hanya perlu mengatur
Setelah ☝️ baris ini, Swift secara otomatis akan mencocokkan semua
snake_case
bidang dari JSON kecamelCase
bidang dalam model Swift.Misalnya
Berikut kode lengkapnya
1. Menulis Model
struct Response: Codable { let id: Int let user: User let reviewsCount: [ReviewCount] struct User: Codable { let userName: String struct RealInfo: Codable { let fullName: String } } struct ReviewCount: Codable { let count: Int } }
2. Mengatur Decoder
let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase
3. Decoding
do { let response = try? decoder.decode(Response.self, from: data) print(response) } catch { debugPrint(error) }
sumber
let file = "data.json" guard let url = Bundle.main.url(forResource: "data", withExtension: "json") else{ fatalError("Failed to locate \(file) in bundle.") } guard let data = try? Data(contentsOf: url) else{ fatalError("Failed to locate \(file) in bundle.") } let yourObject = try? JSONDecoder().decode(YourModel.self, from: data)
sumber
jsonStr
, Anda dapat menggunakan ini sebagai pengganti duaguard let
s di atas:guard let jsonStrData: Data? = jsonStr.data(using: .utf8)! else { print("error") }
kemudian konversikanjsonStrData
ke struct Anda seperti yang dijelaskan di atas padalet yourObject
barisAnda juga dapat menggunakan perpustakaan KeyedCodable yang saya siapkan. Ini akan membutuhkan lebih sedikit kode. Beri tahu saya pendapat Anda tentang itu.
struct ServerResponse: Decodable, Keyedable { var id: String! var username: String! var fullName: String! var reviewCount: Int! private struct ReviewsCount: Codable { var count: Int } mutating func map(map: KeyMap) throws { var id: Int! try id <<- map["id"] self.id = String(id) try username <<- map["user.user_name"] try fullName <<- map["user.real_info.full_name"] var reviewCount: [ReviewsCount]! try reviewCount <<- map["reviews_count"] self.reviewCount = reviewCount[0].count } init(from decoder: Decoder) throws { try KeyedDecoder(with: decoder).decode(to: &self) } }
sumber