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. 👇🏻
- 这是我关于将Codable与嵌套JSON树一起使用的文章的后续内容。 如果你还没有读过那个,那么首先赶上那里,因为我将捎带我所涵盖的内容。👇🏻
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?!
- 所以现在我们都在同一页上,让我们谈谈每个人都需要帮助的一件事:我如何使用CODABLE OMG从JSON响应中获得一个对象阵列?!
(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):
- 是的,这个让我困扰了一会儿。 老实说,我认为解决方案并不令人惊讶,但它现在有效,在某些情况下实际上仍然比使用JSONSerialization或第三方库更好。 那么,让我们深入研究它。 以下是我在这篇文章中使用的数据(你会发现它比上次有点棘手):
{"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数据中的属性。 但是它的神奇之处在于,如果Codable的元素也已经符合它,它将自动符合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.
- 所以这看起来很简单,对吧? 我们只是告诉编译器如何使用我们知道我们将要提供的解码器来创建这个对象,以及如何使用我们也知道我们将要提供的编码器来分解它。
‼️ 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:
- 你想确保你明白这里发生了什么。 此对象仅准备使用如下所示的数据:
{
"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:
- 所以现在我们的响应模型看起来像这样:
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.
- 你有它! 当然,最实际的例子是当你获得具有一个对象数组的根容器“响应”的JSON数据时,但这种方法将起到同样的作用。
And that’s the waaaaaaaaaaaaaaay the news goes!
- 这就是新闻发布的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以便您可以简单地提供特定的键甚至路径并告诉编译器在该路径上为您提供对象或对象数组。 在这个例子中,如果你没有或者不需要“Bar”和“Baz”属性,那么只需要创建一个Response对象就可以保存你的[Friends]。 这是在Objective-C和其他第三方库(如SwiftyJSON)中可用的东西,在这里也可以看到这个功能。 我想我们会看到会发生什么!¯\ (ツ) /¯
网友评论