您的第一个示例无法编译(并且您的第二个示例崩溃)的原因是因为协议不符合自己-不是符合Tag
的类型Codable
,因此也不符合[Tag]
. 因此Article
不会获得自动生成的Codable
一致性,因为并非所有属性都符合Codable
.
仅对协议中列出的属性进行编码和解码
如果您只想对协议中列出的属性进行编码和解码,一种解决方案是简单地使用AnyTag
仅包含这些属性的类型擦除器,然后可以提供Codable
一致性。
然后,您可以拥有Article
此类型擦除包装器的数组,而不是Tag
:
struct AnyTag : Tag, Codable {
let type: String
let value: String
init(_ base: Tag) {
self.type = base.type
self.value = base.value
}
}
struct Article: Codable {
let tags: [AnyTag]
let title: String
}
let tags: [Tag] = [
AuthorTag(value: "Author Tag Value"),
GenreTag(value:"Genre Tag Value")
]
let article = Article(tags: tags.map(AnyTag.init), title: "Article Title")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(article)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
输出以下 JSON 字符串:
{
"title" : "Article Title",
"tags" : [
{
"type" : "author",
"value" : "Author Tag Value"
},
{
"type" : "genre",
"value" : "Genre Tag Value"
}
]
}
并且可以像这样解码:
let decoded = try JSONDecoder().decode(Article.self, from: jsonData)
print(decoded)
// Article(tags: [
// AnyTag(type: "author", value: "Author Tag Value"),
// AnyTag(type: "genre", value: "Genre Tag Value")
// ], title: "Article Title")
编码和解码符合类型的所有属性
但是,如果您需要对给定符合类型的每个Tag
属性进行编码和解码,您可能希望以某种方式将类型信息存储在 JSON 中。
我会使用一个enum
来做到这一点:
enum TagType : String, Codable {
// be careful not to rename these – the encoding/decoding relies on the string
// values of the cases. If you want the decoding to be reliant on case
// position rather than name, then you can change to enum TagType : Int.
// (the advantage of the String rawValue is that the JSON is more readable)
case author, genre
var metatype: Tag.Type {
switch self {
case .author:
return AuthorTag.self
case .genre:
return GenreTag.self
}
}
}
这比仅使用纯字符串来表示类型要好,因为编译器可以检查我们是否为每种情况提供了元类型。
然后您只需要更改Tag
协议,使其需要符合类型来实现static
描述其类型的属性:
protocol Tag : Codable {
static var type: TagType { get }
var value: String { get }
}
struct AuthorTag : Tag {
static var type = TagType.author
let value: String
var foo: Float
}
struct GenreTag : Tag {
static var type = TagType.genre
let value: String
var baz: String
}
然后我们需要调整类型擦除包装器的实现,以便TagType
与 base 一起编码和解码Tag
:
struct AnyTag : Codable {
var base: Tag
init(_ base: Tag) {
self.base = base
}
private enum CodingKeys : CodingKey {
case type, base
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(TagType.self, forKey: .type)
self.base = try type.metatype.init(from: container.superDecoder(forKey: .base))
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type(of: base).type, forKey: .type)
try base.encode(to: container.superEncoder(forKey: .base))
}
}
我们正在使用超级编码器/解码器,以确保给定符合类型的属性键不会与用于编码该类型的键冲突。例如,编码后的 JSON 将如下所示:
{
"type" : "author",
"base" : {
"value" : "Author Tag Value",
"foo" : 56.7
}
}
但是,如果您知道不会发生冲突,并且希望属性在与“类型”键相同的级别进行编码/解码,则 JSON 看起来像这样:
{
"type" : "author",
"value" : "Author Tag Value",
"foo" : 56.7
}
您可以在上面的代码中传递decoder
而不是container.superDecoder(forKey: .base)
&encoder
而不是。container.superEncoder(forKey: .base)
作为可选步骤,我们可以自定义Codable
实现Article
,而不是依赖于自动生成的与tags
type 属性的一致性[AnyTag]
,我们可以提供我们自己的实现,将 a 装箱[Tag]
到[AnyTag]
编码前,然后拆箱进行解码:
struct Article {
let tags: [Tag]
let title: String
init(tags: [Tag], title: String) {
self.tags = tags
self.title = title
}
}
extension Article : Codable {
private enum CodingKeys : CodingKey {
case tags, title
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.tags = try container.decode([AnyTag].self, forKey: .tags).map { $0.base }
self.title = try container.decode(String.self, forKey: .title)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(tags.map(AnyTag.init), forKey: .tags)
try container.encode(title, forKey: .title)
}
}
这允许我们将tags
属性设置为类型[Tag]
,而不是[AnyTag]
。
现在我们可以对枚举Tag
中列出的任何符合要求的类型进行编码和解码:TagType
let tags: [Tag] = [
AuthorTag(value: "Author Tag Value", foo: 56.7),
GenreTag(value:"Genre Tag Value", baz: "hello world")
]
let article = Article(tags: tags, title: "Article Title")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(article)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
输出 JSON 字符串:
{
"title" : "Article Title",
"tags" : [
{
"type" : "author",
"base" : {
"value" : "Author Tag Value",
"foo" : 56.7
}
},
{
"type" : "genre",
"base" : {
"value" : "Genre Tag Value",
"baz" : "hello world"
}
}
]
}
然后可以像这样解码:
let decoded = try JSONDecoder().decode(Article.self, from: jsonData)
print(decoded)
// Article(tags: [
// AuthorTag(value: "Author Tag Value", foo: 56.7000008),
// GenreTag(value: "Genre Tag Value", baz: "hello world")
// ],
// title: "Article Title")