如何在 Swift 4 中实现 JSON 数据的多态解码?
How can I implement polymorphic decoding of JSON data in Swift 4?
我正在尝试根据从 API 端点返回的数据呈现视图。我的 JSON 看起来(大致)像这样:
{
"sections": [
{
"title": "Featured",
"section_layout_type": "featured_panels",
"section_items": [
{
"item_type": "foo",
"id": 3,
"title": "Bisbee1",
"audio_url": "http://example.com/foo1.mp3",
"feature_image_url" : "http://example.com/feature1.jpg"
},
{
"item_type": "bar",
"id": 4,
"title": "Mortar8",
"video_url": "http://example.com/video.mp4",
"director" : "John Smith",
"feature_image_url" : "http://example.com/feature2.jpg"
}
]
}
]
}
我有一个 object 表示如何在我的 UI 中布局视图。它看起来像这样:
public struct ViewLayoutSection : Codable {
var title: String = ""
var sectionLayoutType: String
var sectionItems: [ViewLayoutSectionItemable] = []
}
ViewLayoutSectionItemable
是一种协议,其中包括标题和要在布局中使用的图像的 URL。
然而,sectionItems
数组实际上是由不同的类型组成的。我想做的是将每个部分项目实例化为它自己的实例 class.
如何为 ViewLayoutSection
设置 init(from decoder: Decoder)
方法,让我遍历 JSON 数组中的项目并创建正确 class 的实例在每种情况下?
我建议您谨慎使用 Codable
。如果您只想从 JSON 解码一个类型而不对其进行编码,那么仅使其符合 Decodable
就足够了。由于您已经发现需要手动解码(通过 init(from decoder: Decoder)
的自定义实现),问题就变成了:最不痛苦的方法是什么?
首先,数据模型。请注意 ViewLayoutSectionItemable
及其采用者不符合 Decodable
:
enum ItemType: String, Decodable {
case foo
case bar
}
protocol ViewLayoutSectionItemable {
var id: Int { get }
var itemType: ItemType { get }
var title: String { get set }
var imageURL: URL { get set }
}
struct Foo: ViewLayoutSectionItemable {
let id: Int
let itemType: ItemType
var title: String
var imageURL: URL
// Custom properties of Foo
var audioURL: URL
}
struct Bar: ViewLayoutSectionItemable {
let id: Int
let itemType: ItemType
var title: String
var imageURL: URL
// Custom properties of Bar
var videoURL: URL
var director: String
}
接下来,我们将如何解码 JSON:
struct Sections: Decodable {
var sections: [ViewLayoutSection]
}
struct ViewLayoutSection: Decodable {
var title: String = ""
var sectionLayoutType: String
var sectionItems: [ViewLayoutSectionItemable] = []
// This struct use snake_case to match the JSON so we don't have to provide a custom
// CodingKeys enum. And since it's private, outside code will never see it
private struct GenericItem: Decodable {
let id: Int
let item_type: ItemType
var title: String
var feature_image_url: URL
// Custom properties of all possible types. Note that they are all optionals
var audio_url: URL?
var video_url: URL?
var director: String?
}
private enum CodingKeys: String, CodingKey {
case title
case sectionLayoutType = "section_layout_type"
case sectionItems = "section_items"
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
sectionLayoutType = try container.decode(String.self, forKey: .sectionLayoutType)
sectionItems = try container.decode([GenericItem].self, forKey: .sectionItems).map { item in
switch item.item_type {
case .foo:
// It's OK to force unwrap here because we already
// know what type the item object is
return Foo(id: item.id, itemType: item.item_type, title: item.title, imageURL: item.feature_image_url, audioURL: item.audio_url!)
case .bar:
return Bar(id: item.id, itemType: item.item_type, title: item.title, imageURL: item.feature_image_url, videoURL: item.video_url!, director: item.director!)
}
}
}
用法:
let sections = try JSONDecoder().decode(Sections.self, from: json).sections
多态设计是一件好事:许多设计模式都表现出多态性,使整个系统更加灵活和可扩展。
不幸的是,Codable
没有对多态性的“内置”支持,至少现在还没有……还有关于是否 this is actually a feature or a bug.
的讨论
幸运的是,您可以使用 enum
作为中间“包装器”轻松创建多态对象。
首先,我建议将 itemType
声明为 static
属性,而不是实例 属性,以便以后更容易打开它。因此,您的协议和多态类型将如下所示:
import Foundation
public protocol ViewLayoutSectionItemable: Decodable {
static var itemType: String { get }
var id: Int { get }
var title: String { get set }
var imageURL: URL { get set }
}
public struct Foo: ViewLayoutSectionItemable {
// ViewLayoutSectionItemable Properties
public static var itemType: String { return "foo" }
public let id: Int
public var title: String
public var imageURL: URL
// Foo Properties
public var audioURL: URL
}
public struct Bar: ViewLayoutSectionItemable {
// ViewLayoutSectionItemable Properties
public static var itemType: String { return "bar" }
public let id: Int
public var title: String
public var imageURL: URL
// Bar Properties
public var director: String
public var videoURL: URL
}
接下来,为“包装器”创建一个枚举:
public enum ItemableWrapper: Decodable {
// 1. Keys
fileprivate enum Keys: String, CodingKey {
case itemType = "item_type"
case sections
case sectionItems = "section_items"
}
// 2. Cases
case foo(Foo)
case bar(Bar)
// 3. Computed Properties
public var item: ViewLayoutSectionItemable {
switch self {
case .foo(let item): return item
case .bar(let item): return item
}
}
// 4. Static Methods
public static func items(from decoder: Decoder) -> [ViewLayoutSectionItemable] {
guard let container = try? decoder.container(keyedBy: Keys.self),
var sectionItems = try? container.nestedUnkeyedContainer(forKey: .sectionItems) else {
return []
}
var items: [ViewLayoutSectionItemable] = []
while !sectionItems.isAtEnd {
guard let wrapper = try? sectionItems.decode(ItemableWrapper.self) else { continue }
items.append(wrapper.item)
}
return items
}
// 5. Decodable
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
let itemType = try container.decode(String.self, forKey: Keys.itemType)
switch itemType {
case Foo.itemType: self = .foo(try Foo(from: decoder))
case Bar.itemType: self = .bar(try Bar(from: decoder))
default:
throw DecodingError.dataCorruptedError(forKey: .itemType,
in: container,
debugDescription: "Unhandled item type: \(itemType)")
}
}
}
上面的内容是这样的:
您声明 Keys
与响应的结构相关。在给定的 API 中,您对 sections
和 sectionItems
感兴趣。您还需要知道哪个键代表类型,您在这里声明为 itemType
.
然后您明确列出所有可能的情况:这违反了Open Closed Principle,但这样做“没问题”,因为它充当用于创建项目的“工厂”....
基本上,在整个应用程序中,您只会一次,就在这里。
您为 item
声明了一个计算 属性:这样,您可以解包底层 ViewLayoutSectionItemable
而无需 需要关心实际的 case
.
这是“包装器”工厂的核心:您将 items(from:)
声明为能够返回 [ViewLayoutSectionItemable]
的 static
方法,这正是你想做的:传入一个 Decoder
并取回一个包含多态类型的数组!这是您实际使用的方法,而不是直接解码 Foo
、Bar
或这些类型的任何其他多态数组。
最后,你必须让ItemableWrapper
实现Decodable
方法。这里的技巧是 ItemWrapper
总是 解码 ItemWrapper
:因此,这就是 Decodable
所期望的。
但是,由于它是一个 enum
,因此它允许具有关联类型,这正是您对每种情况所做的。因此,您可以间接创建多态类型!
由于您在 ItemWrapper
中完成了所有繁重的工作,现在 非常 很容易从 Decoder
转到 `[ViewLayoutSectionItemable] ,您只需像这样:
let decoder = ... // however you created it
let items = ItemableWrapper.items(from: decoder)
@CodeDifferent 回复的更简单版本,解决了@JRG-Developer 的评论。无需重新考虑您的 JSON API;这是一个常见的场景。对于您创建的每一个新的ViewLayoutSectionItem
,您只需要分别在PartiallyDecodedItem.ItemKind
枚举和PartiallyDecodedItem.init(from:)
方法中添加一个案例和一行代码。
与接受的答案相比,这不仅代码量最少,而且性能更高。在@CodeDifferent 的选项中,您需要使用 2 种不同的数据表示来初始化 2 个数组,以获得 ViewLayoutSectionItem
的数组。在此选项中,您仍然需要初始化 2 个数组,但通过利用 copy-on-write 语义,只能获得一种数据表示形式。
另请注意,没有必要在协议或采用的结构中包含 ItemType
(在静态类型语言中包含描述类型的字符串没有意义)。
protocol ViewLayoutSectionItem {
var id: Int { get }
var title: String { get }
var imageURL: URL { get }
}
struct Foo: ViewLayoutSectionItem {
let id: Int
let title: String
let imageURL: URL
let audioURL: URL
}
struct Bar: ViewLayoutSectionItem {
let id: Int
let title: String
let imageURL: URL
let videoURL: URL
let director: String
}
private struct PartiallyDecodedItem: Decodable {
enum ItemKind: String, Decodable {
case foo, bar
}
let kind: Kind
let item: ViewLayoutSectionItem
private enum DecodingKeys: String, CodingKey {
case kind = "itemType"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DecodingKeys.self)
self.kind = try container.decode(Kind.self, forKey: .kind)
self.item = try {
switch kind {
case .foo: return try Foo(from: decoder)
case .number: return try Bar(from: decoder)
}()
}
}
struct ViewLayoutSection: Decodable {
let title: String
let sectionLayoutType: String
let sectionItems: [ViewLayoutSectionItem]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.title = try container.decode(String.self, forKey: .title)
self.sectionLayoutType = try container.decode(String.self, forKey: .sectionLayoutType)
self.sectionItems = try container.decode([PartiallyDecodedItem].self, forKey: .sectionItems)
.map { [=10=].item }
}
}
要处理蛇形大小写 -> 驼峰大小写转换,而不是手动输入所有的键,您可以简单地在 JSONDecoder
上设置一个 属性
struct Sections: Decodable {
let sections: [ViewLayoutSection]
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let sections = try decode(Sections.self, from: json)
.sections
我已经写了一篇关于这个确切问题的 blog post。
综上所述。我建议在 Decoder
上定义一个扩展
extension Decoder {
func decode<ExpectedType>(_ expectedType: ExpectedType.Type) throws -> ExpectedType {
let container = try self.container(keyedBy: PolymorphicMetaContainerKeys.self)
let typeID = try container.decode(String.self, forKey: .itemType)
guard let types = self.userInfo[.polymorphicTypes] as? [Polymorphic.Type] else {
throw PolymorphicCodableError.missingPolymorphicTypes
}
let matchingType = types.first { type in
type.id == typeID
}
guard let matchingType = matchingType else {
throw PolymorphicCodableError.unableToFindPolymorphicType(typeID)
}
let decoded = try matchingType.init(from: self)
guard let decoded = decoded as? ExpectedType else {
throw PolymorphicCodableError.unableToCast(
decoded: decoded,
into: String(describing: ExpectedType.self)
)
}
return decoded
}
}
然后将可能的多态类型添加到 Decoder
实例:
var decoder = JSONDecoder()
decoder.userInfo[.polymorphicTypes] = [
Snake.self,
Dog.self
]
如果你有嵌套的聚合值,你可以编写一个 属性 包装器来调用这个解码方法,这样你就不需要定义自定义 init(from:)
.
这是解决这个确切问题的小 utility package。
它是围绕一个配置类型构建的,该配置类型具有可解码类型的变体,定义了类型信息 discriminator
。
enum DrinkFamily: String, ClassFamily {
case drink = "drink"
case beer = "beer"
static var discriminator: Discriminator = .type
typealias BaseType = Drink
func getType() -> Drink.Type {
switch self {
case .beer:
return Beer.self
case .drink:
return Drink.self
}
}
}
稍后在您的集合中重载 init 方法以使用我们的 KeyedDecodingContainer
扩展。
class Bar: Decodable {
let drinks: [Drink]
private enum CodingKeys: String, CodingKey {
case drinks
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
drinks = try container.decodeHeterogeneousArray(OfFamily: DrinkFamily.self, forKey: .drinks)
}
}
我正在尝试根据从 API 端点返回的数据呈现视图。我的 JSON 看起来(大致)像这样:
{
"sections": [
{
"title": "Featured",
"section_layout_type": "featured_panels",
"section_items": [
{
"item_type": "foo",
"id": 3,
"title": "Bisbee1",
"audio_url": "http://example.com/foo1.mp3",
"feature_image_url" : "http://example.com/feature1.jpg"
},
{
"item_type": "bar",
"id": 4,
"title": "Mortar8",
"video_url": "http://example.com/video.mp4",
"director" : "John Smith",
"feature_image_url" : "http://example.com/feature2.jpg"
}
]
}
]
}
我有一个 object 表示如何在我的 UI 中布局视图。它看起来像这样:
public struct ViewLayoutSection : Codable {
var title: String = ""
var sectionLayoutType: String
var sectionItems: [ViewLayoutSectionItemable] = []
}
ViewLayoutSectionItemable
是一种协议,其中包括标题和要在布局中使用的图像的 URL。
然而,sectionItems
数组实际上是由不同的类型组成的。我想做的是将每个部分项目实例化为它自己的实例 class.
如何为 ViewLayoutSection
设置 init(from decoder: Decoder)
方法,让我遍历 JSON 数组中的项目并创建正确 class 的实例在每种情况下?
我建议您谨慎使用 Codable
。如果您只想从 JSON 解码一个类型而不对其进行编码,那么仅使其符合 Decodable
就足够了。由于您已经发现需要手动解码(通过 init(from decoder: Decoder)
的自定义实现),问题就变成了:最不痛苦的方法是什么?
首先,数据模型。请注意 ViewLayoutSectionItemable
及其采用者不符合 Decodable
:
enum ItemType: String, Decodable {
case foo
case bar
}
protocol ViewLayoutSectionItemable {
var id: Int { get }
var itemType: ItemType { get }
var title: String { get set }
var imageURL: URL { get set }
}
struct Foo: ViewLayoutSectionItemable {
let id: Int
let itemType: ItemType
var title: String
var imageURL: URL
// Custom properties of Foo
var audioURL: URL
}
struct Bar: ViewLayoutSectionItemable {
let id: Int
let itemType: ItemType
var title: String
var imageURL: URL
// Custom properties of Bar
var videoURL: URL
var director: String
}
接下来,我们将如何解码 JSON:
struct Sections: Decodable {
var sections: [ViewLayoutSection]
}
struct ViewLayoutSection: Decodable {
var title: String = ""
var sectionLayoutType: String
var sectionItems: [ViewLayoutSectionItemable] = []
// This struct use snake_case to match the JSON so we don't have to provide a custom
// CodingKeys enum. And since it's private, outside code will never see it
private struct GenericItem: Decodable {
let id: Int
let item_type: ItemType
var title: String
var feature_image_url: URL
// Custom properties of all possible types. Note that they are all optionals
var audio_url: URL?
var video_url: URL?
var director: String?
}
private enum CodingKeys: String, CodingKey {
case title
case sectionLayoutType = "section_layout_type"
case sectionItems = "section_items"
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
sectionLayoutType = try container.decode(String.self, forKey: .sectionLayoutType)
sectionItems = try container.decode([GenericItem].self, forKey: .sectionItems).map { item in
switch item.item_type {
case .foo:
// It's OK to force unwrap here because we already
// know what type the item object is
return Foo(id: item.id, itemType: item.item_type, title: item.title, imageURL: item.feature_image_url, audioURL: item.audio_url!)
case .bar:
return Bar(id: item.id, itemType: item.item_type, title: item.title, imageURL: item.feature_image_url, videoURL: item.video_url!, director: item.director!)
}
}
}
用法:
let sections = try JSONDecoder().decode(Sections.self, from: json).sections
多态设计是一件好事:许多设计模式都表现出多态性,使整个系统更加灵活和可扩展。
不幸的是,Codable
没有对多态性的“内置”支持,至少现在还没有……还有关于是否 this is actually a feature or a bug.
幸运的是,您可以使用 enum
作为中间“包装器”轻松创建多态对象。
首先,我建议将 itemType
声明为 static
属性,而不是实例 属性,以便以后更容易打开它。因此,您的协议和多态类型将如下所示:
import Foundation
public protocol ViewLayoutSectionItemable: Decodable {
static var itemType: String { get }
var id: Int { get }
var title: String { get set }
var imageURL: URL { get set }
}
public struct Foo: ViewLayoutSectionItemable {
// ViewLayoutSectionItemable Properties
public static var itemType: String { return "foo" }
public let id: Int
public var title: String
public var imageURL: URL
// Foo Properties
public var audioURL: URL
}
public struct Bar: ViewLayoutSectionItemable {
// ViewLayoutSectionItemable Properties
public static var itemType: String { return "bar" }
public let id: Int
public var title: String
public var imageURL: URL
// Bar Properties
public var director: String
public var videoURL: URL
}
接下来,为“包装器”创建一个枚举:
public enum ItemableWrapper: Decodable {
// 1. Keys
fileprivate enum Keys: String, CodingKey {
case itemType = "item_type"
case sections
case sectionItems = "section_items"
}
// 2. Cases
case foo(Foo)
case bar(Bar)
// 3. Computed Properties
public var item: ViewLayoutSectionItemable {
switch self {
case .foo(let item): return item
case .bar(let item): return item
}
}
// 4. Static Methods
public static func items(from decoder: Decoder) -> [ViewLayoutSectionItemable] {
guard let container = try? decoder.container(keyedBy: Keys.self),
var sectionItems = try? container.nestedUnkeyedContainer(forKey: .sectionItems) else {
return []
}
var items: [ViewLayoutSectionItemable] = []
while !sectionItems.isAtEnd {
guard let wrapper = try? sectionItems.decode(ItemableWrapper.self) else { continue }
items.append(wrapper.item)
}
return items
}
// 5. Decodable
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
let itemType = try container.decode(String.self, forKey: Keys.itemType)
switch itemType {
case Foo.itemType: self = .foo(try Foo(from: decoder))
case Bar.itemType: self = .bar(try Bar(from: decoder))
default:
throw DecodingError.dataCorruptedError(forKey: .itemType,
in: container,
debugDescription: "Unhandled item type: \(itemType)")
}
}
}
上面的内容是这样的:
您声明
Keys
与响应的结构相关。在给定的 API 中,您对sections
和sectionItems
感兴趣。您还需要知道哪个键代表类型,您在这里声明为itemType
.然后您明确列出所有可能的情况:这违反了Open Closed Principle,但这样做“没问题”,因为它充当用于创建项目的“工厂”....
基本上,在整个应用程序中,您只会一次,就在这里。
您为
item
声明了一个计算 属性:这样,您可以解包底层ViewLayoutSectionItemable
而无需 需要关心实际的case
.这是“包装器”工厂的核心:您将
items(from:)
声明为能够返回[ViewLayoutSectionItemable]
的static
方法,这正是你想做的:传入一个Decoder
并取回一个包含多态类型的数组!这是您实际使用的方法,而不是直接解码Foo
、Bar
或这些类型的任何其他多态数组。最后,你必须让
ItemableWrapper
实现Decodable
方法。这里的技巧是ItemWrapper
总是 解码ItemWrapper
:因此,这就是Decodable
所期望的。
但是,由于它是一个 enum
,因此它允许具有关联类型,这正是您对每种情况所做的。因此,您可以间接创建多态类型!
由于您在 ItemWrapper
中完成了所有繁重的工作,现在 非常 很容易从 Decoder
转到 `[ViewLayoutSectionItemable] ,您只需像这样:
let decoder = ... // however you created it
let items = ItemableWrapper.items(from: decoder)
@CodeDifferent 回复的更简单版本,解决了@JRG-Developer 的评论。无需重新考虑您的 JSON API;这是一个常见的场景。对于您创建的每一个新的ViewLayoutSectionItem
,您只需要分别在PartiallyDecodedItem.ItemKind
枚举和PartiallyDecodedItem.init(from:)
方法中添加一个案例和一行代码。
与接受的答案相比,这不仅代码量最少,而且性能更高。在@CodeDifferent 的选项中,您需要使用 2 种不同的数据表示来初始化 2 个数组,以获得 ViewLayoutSectionItem
的数组。在此选项中,您仍然需要初始化 2 个数组,但通过利用 copy-on-write 语义,只能获得一种数据表示形式。
另请注意,没有必要在协议或采用的结构中包含 ItemType
(在静态类型语言中包含描述类型的字符串没有意义)。
protocol ViewLayoutSectionItem {
var id: Int { get }
var title: String { get }
var imageURL: URL { get }
}
struct Foo: ViewLayoutSectionItem {
let id: Int
let title: String
let imageURL: URL
let audioURL: URL
}
struct Bar: ViewLayoutSectionItem {
let id: Int
let title: String
let imageURL: URL
let videoURL: URL
let director: String
}
private struct PartiallyDecodedItem: Decodable {
enum ItemKind: String, Decodable {
case foo, bar
}
let kind: Kind
let item: ViewLayoutSectionItem
private enum DecodingKeys: String, CodingKey {
case kind = "itemType"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DecodingKeys.self)
self.kind = try container.decode(Kind.self, forKey: .kind)
self.item = try {
switch kind {
case .foo: return try Foo(from: decoder)
case .number: return try Bar(from: decoder)
}()
}
}
struct ViewLayoutSection: Decodable {
let title: String
let sectionLayoutType: String
let sectionItems: [ViewLayoutSectionItem]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.title = try container.decode(String.self, forKey: .title)
self.sectionLayoutType = try container.decode(String.self, forKey: .sectionLayoutType)
self.sectionItems = try container.decode([PartiallyDecodedItem].self, forKey: .sectionItems)
.map { [=10=].item }
}
}
要处理蛇形大小写 -> 驼峰大小写转换,而不是手动输入所有的键,您可以简单地在 JSONDecoder
struct Sections: Decodable {
let sections: [ViewLayoutSection]
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let sections = try decode(Sections.self, from: json)
.sections
我已经写了一篇关于这个确切问题的 blog post。
综上所述。我建议在 Decoder
extension Decoder {
func decode<ExpectedType>(_ expectedType: ExpectedType.Type) throws -> ExpectedType {
let container = try self.container(keyedBy: PolymorphicMetaContainerKeys.self)
let typeID = try container.decode(String.self, forKey: .itemType)
guard let types = self.userInfo[.polymorphicTypes] as? [Polymorphic.Type] else {
throw PolymorphicCodableError.missingPolymorphicTypes
}
let matchingType = types.first { type in
type.id == typeID
}
guard let matchingType = matchingType else {
throw PolymorphicCodableError.unableToFindPolymorphicType(typeID)
}
let decoded = try matchingType.init(from: self)
guard let decoded = decoded as? ExpectedType else {
throw PolymorphicCodableError.unableToCast(
decoded: decoded,
into: String(describing: ExpectedType.self)
)
}
return decoded
}
}
然后将可能的多态类型添加到 Decoder
实例:
var decoder = JSONDecoder()
decoder.userInfo[.polymorphicTypes] = [
Snake.self,
Dog.self
]
如果你有嵌套的聚合值,你可以编写一个 属性 包装器来调用这个解码方法,这样你就不需要定义自定义 init(from:)
.
这是解决这个确切问题的小 utility package。
它是围绕一个配置类型构建的,该配置类型具有可解码类型的变体,定义了类型信息 discriminator
。
enum DrinkFamily: String, ClassFamily {
case drink = "drink"
case beer = "beer"
static var discriminator: Discriminator = .type
typealias BaseType = Drink
func getType() -> Drink.Type {
switch self {
case .beer:
return Beer.self
case .drink:
return Drink.self
}
}
}
稍后在您的集合中重载 init 方法以使用我们的 KeyedDecodingContainer
扩展。
class Bar: Decodable {
let drinks: [Drink]
private enum CodingKeys: String, CodingKey {
case drinks
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
drinks = try container.decodeHeterogeneousArray(OfFamily: DrinkFamily.self, forKey: .drinks)
}
}