Using Codable With Nested Arrays Is Easier And More Fun Than You Think
This is a follow-up to my article on using Codable with nested JSON trees. If you haven’t read that one, first catch up there since I’ll be piggy-backing on what I’ve covered in it. ????
- 這是我關(guān)于將Codable與嵌套JSON樹一起使用的文章的后續(xù)內(nèi)容。 如果你還沒有讀過那個,那么首先趕上那里,因為我將捎帶我所涵蓋的內(nèi)容。????
So now that we’re all on the same page, let’s talk about the one thing everyone needs a little help with: HOW DO I GET AN ARRAY OF OBJECTS FROM JSON RESPONSE USING CODABLE OMG?!
- 所以現(xiàn)在我們都在同一頁上,讓我們談?wù)劽總€人都需要幫助的一件事:我如何使用CODABLE OMG從JSON響應(yīng)中獲得一個對象陣列?!
(For you TL;DRers, the full code is at the bottom)
Yeah, this one stumped me for a while. And honestly, I think the solution isn’t amazing, but it works for now, and in some cases is actually still better than using JSONSerialization or a third-party library. So, let’s dig into it. Below is the data I’m using for this post (you’ll notice it’s a little trickier than last time):
- 是的,這個讓我困擾了一會兒。 老實說,我認為解決方案并不令人驚訝,但它現(xiàn)在有效,在某些情況下實際上仍然比使用JSONSerialization或第三方庫更好。 那么,讓我們深入研究它。 以下是我在這篇文章中使用的數(shù)據(jù)(你會發(fā)現(xiàn)它比上次有點棘手):
{"Response": {
"Bar": true,
"Baz": "Hello, World!",
"Friends": [
{"FirstName": "Gabe",
"FavoriteColor": "Orange"},
{"FirstName": "Jeremiah",
"FavoriteColor": "Green"},
{"FirstName": "Peter",
"FavoriteColor": "Red"}]}}
“Yikes, that’s trouble!” you might think. But actually, it’s not so bad! See, the real magic of Codable is in how it works. If you think back to the last article, we were able to decode properties from our JSON data simply by telling the decoder what container it’s in, what type we’re expecting out of it, and what key to use to get it. But what’s so magical about it is that Array will automatically conform to Codable if its elements also already conform to it. So just follow the same steps (tell the decoder to access a key in the container and get back the type you’re expecting), and voila! It’s really that simple.
- “哎呀,這很麻煩!”你可能會想。 但實際上,它并沒有那么糟糕! 看,Codable真正的神奇之處在于它是如何工作的。 如果你回想一下上一篇文章,我們就可以通過告訴解碼器它所在的容器,我們期待它的類型以及使用它來獲取它的密鑰來解碼我們的JSON數(shù)據(jù)中的屬性。 但是它的神奇之處在于,如果Codable的元素也已經(jīng)符合它,它將自動符合Codable。 因此,只需按照相同的步驟(告訴解碼器訪問容器中的密鑰并返回您期望的類型),瞧! 這真的很簡單。
So let’s start with a model for a friend. We want an object that holds their first name and favorite color. Following the steps from the last article, it might look something like this:
- 讓我們從一個朋友的模型開始吧。 我們想要一個擁有他們的名字和喜歡的顏色的對象。 按照上一篇文章中的步驟,它可能看起來像這樣:
struct Friend: Codable {
let firstName: String
let favoriteColor: String
enum CodingKeys: String, CodingKey {
case firstName = "FirstName"
case favoriteColor = "FavoriteColor"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.firstName = try container.decode(String.self, forKey: .firstName)
self.favoriteColor = try container.decode(String.self, forKey: .favoriteColor)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self
try container.encode(self.firstName, forKey: .firstName)
try container.encode(self.favoriteColor, forKey: .favoriteColor)
}
}
So this seems pretty straightforward, right? We are just telling the compiler how to create this object using a Decoder that we know we’re going to provide, and how to break it down using an Encoder that we also know we’re going to provide.
- 所以這看起來很簡單,對吧? 我們只是告訴編譯器如何使用我們知道我們將要提供的解碼器來創(chuàng)建這個對象,以及如何使用我們也知道我們將要提供的編碼器來分解它。
?? DON’T MISS THIS ?? ???? !DO不要錯過這個!?????
You want to make sure you understand what’s going on here. This object is only prepared to work with data that looks like this:
- 你想確保你明白這里發(fā)生了什么。 此對象僅準備使用如下所示的數(shù)據(jù):
{
"FirstName": String,
"FavoriteColor": String
}
BUT since this model conforms to Codable, Swift 4 automatically gives us an ability to make an Array<Friend> using the same tool! ????
- 但是由于這個模型符合Codable,Swift 4自動讓我們能夠使用相同的工具制作一個Array <Friend>!????
So now our response model would look like this:
- 所以現(xiàn)在我們的響應(yīng)模型看起來像這樣:
struct Response: Codable {
let bar: Bool
let baz: String
let friends: [Friend]
enum CodingKeys: String, CodingKey {
case response = "Response"
case bar = "Bar"
case baz = "Baz"
case friends = "Friends"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let response = try container.nestedContainer(keyedBy:
CodingKeys.self, forKey: .response)
self.bar = try response.decode(Bool.self, forKey: .bar)
self.baz = try response.decode(String.self, forKey: .baz)
self.friends = try response.decode([Friend].self, forKey: .friends)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
var response = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .response)
try response.encode(self.bar, forKey: .bar)
try response.encode(self.baz, forKey: .baz)
var friends = response.nestedContainer(keyedBy: CodingKeys.self, forKey: .friends)
try friends.encode(self.friends, forKey: .friends)
}
}
There you have it! Of course, the most practical example of this is when you get back JSON data that has a root container of "Response" with an array of objects, but this approach will work just the same.
- 你有它! 當然,最實際的例子是當你獲得具有一個對象數(shù)組的根容器“響應(yīng)”的JSON數(shù)據(jù)時,但這種方法將起到同樣的作用。
And that’s the waaaaaaaaaaaaaaay the news goes!
- 這就是新聞發(fā)布的waaaaaaaaaaaaaay!
FULL CODE:
let jsonString = """
{"Response": {
"Bar": true,
"Baz": "Hello, World!",
"Friends": [
{"FirstName": "Gabe",
"FavoriteColor": "Orange"},
{"FirstName": "Jeremiah",
"FavoriteColor": "Green"},
{"FirstName": "Peter",
"FavoriteColor": "Red"}]}}
"""
struct Friend: Codable {
let firstName: String
let favoriteColor: String
enum CodingKeys: String, CodingKey {
case firstName = "FirstName"
case favoriteColor = "FavoriteColor"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.firstName = try container.decode(String.self, forKey: .firstName)
self.favoriteColor = try container.decode(String.self, forKey: .favoriteColor)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.firstName, forKey: .firstName)
try container.encode(self.favoriteColor, forKey: .favoriteColor)
}
}
struct Response: Codable {
let bar: Bool
let baz: String
let friends: [Friend]
enum CodingKeys: String, CodingKey {
case response = "Response"
case bar = "Bar"
case baz = "Baz"
case friends = "Friends"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let response = try container.nestedContainer(keyedBy:
CodingKeys.self, forKey: .response)
self.bar = try response.decode(Bool.self, forKey: .bar)
self.baz = try response.decode(String.self, forKey: .baz)
self.friends = try response.decode([Friend].self, forKey: .friends)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
var response = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .response)
try response.encode(self.bar, forKey: .bar)
try response.encode(self.baz, forKey: .baz)
var friends = response.nestedContainer(keyedBy: CodingKeys.self, forKey: .friends)
try friends.encode(self.friends, forKey: .friends)
}
}
let data = jsonString.data(using: .utf8)!
// Initializes a Response object from the JSON data at the top.
let myResponse = try! JSONDecoder().decode(Response.self, from: data)
// Turns your Response object into raw JSON data you can send back!
let dataToSend = try! JSONEncoder().encode(myResponse)
Note:
I mentioned at the beginning that I don’t think this is an amazing solution, and that’s only half-true. In this instance, this obviously works great, but what would be really nice would be to expand Codable so that you could simply provide a specific key or even a path and tell the compiler to give you an object or array of objects at that path. In this example, if you don’t get or have need for the "Bar" and "Baz" properties, it kinda sucks to have to still create a Response object simply to hold your [Friends]. This is something that’s available in Objective-C and other third-party libraries like SwiftyJSON, and it would be nice to see that functionality here as well. I guess we’ll see what happens! ˉ_(ツ)_/ˉ
- 我在開始時提到過,我認為這不是一個令人驚訝的解決方案,而且這只是半真的。 在這個例子中,這顯然很有效,但是真正好的是擴展Codable以便您可以簡單地提供特定的鍵甚至路徑并告訴編譯器在該路徑上為您提供對象或?qū)ο髷?shù)組。 在這個例子中,如果你沒有或者不需要“Bar”和“Baz”屬性,那么只需要創(chuàng)建一個Response對象就可以保存你的[Friends]。 這是在Objective-C和其他第三方庫(如SwiftyJSON)中可用的東西,在這里也可以看到這個功能。 我想我們會看到會發(fā)生什么!ˉ\ (ツ) /ˉ