Anstatt eine große CodingKeys
Aufzählung mit allen Schlüsseln zu haben, die Sie zum Dekodieren des JSON benötigen, würde ich empfehlen, die Schlüssel für jedes Ihrer verschachtelten JSON-Objekte aufzuteilen und verschachtelte Aufzählungen zu verwenden, um die Hierarchie beizubehalten:
// 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
}
}
Dies erleichtert das Verfolgen der Schlüssel auf jeder Ebene in Ihrem JSON.
Nun bedenken Sie Folgendes:
Ein Schlüsselcontainer wird zum Dekodieren eines JSON-Objekts verwendet und mit einem CodingKey
konformen Typ (wie den oben definierten) dekodiert .
Ein Container ohne Schlüssel wird zum Decodieren eines JSON-Arrays verwendet und nacheinander decodiert (dh jedes Mal, wenn Sie eine Decodierungs- oder verschachtelte Containermethode aufrufen, wird zum nächsten Element im Array übergegangen). Im zweiten Teil der Antwort erfahren Sie, wie Sie eine durchlaufen können.
Nachdem Sie Ihren Schlüsselcontainer der obersten Ebene mit container(keyedBy:)
(da Sie ein JSON-Objekt auf der obersten Ebene haben) vom Decoder abgerufen haben, können Sie die folgenden Methoden wiederholt verwenden:
Beispielsweise:
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)
}
}
Beispieldecodierung:
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)
Durch einen nicht verschlüsselten Behälter iterieren
Betrachten Sie den Fall, wo Sie wollen reviewCount
eine sein [Int]
, wobei jedes Element den Wert darstellt , für den "count"
Schlüssel in der verschachtelten JSON:
"reviews_count": [
{
"count": 4
},
{
"count": 5
}
]
Sie müssen den verschachtelten Container ohne Schlüssel durchlaufen, den verschachtelten Container mit Schlüssel bei jeder Iteration abrufen und den Wert für den "count"
Schlüssel dekodieren . Du kannst den ... benutzencount
Eigenschaft des nicht verschlüsselten Containers verwenden, um das resultierende Array vorab zuzuweisen, und dann dasisAtEnd
Eigenschaft, um es zu durchlaufen.
Beispielsweise:
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)
)
}
}
}