我正在尝试找到使用Swift 4中新的JSONDecoder / Encoder对符合swift协议的结构数组进行编码/解码的最佳方法。
我做了一个小例子来说明这个问题:
首先,我们有一个协议标签和一些符合该协议的类型。
protocol Tag: Codable {
var type: String { get }
var value: String { get }
}
struct AuthorTag: Tag {
let type = "author"
let value: String
}
struct GenreTag: Tag {
let type = "genre"
let value: String
}
然后我们有一个带有标签数组的Type Article。
struct Article: Codable {
let tags: [Tag]
let title: String
}
最后,我们对文章进行编码或解码
let article = Article(tags: [AuthorTag(value: "Author Tag Value"), GenreTag(value:"Genre Tag Value")], title: "Article Title")
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(article)
let jsonString = String(data: jsonData, encoding: .utf8)
这是我喜欢的JSON结构。
{
"title": "Article Title",
"tags": [
{
"type": "author",
"value": "Author Tag Value"
},
{
"type": "genre",
"value": "Genre Tag Value"
}
]
}
问题是,在某些时候,我必须打开type属性以解码Array,但是要解码Array,我必须知道其类型。
编辑:
对我来说很清楚,为什么“可分解的东西”不能开箱即用,但至少“可编码的”应该可以工作。下面的修改后的Article结构可以编译,但由于以下错误消息而崩溃。
fatal error: Array<Tag> does not conform to Encodable because Tag does not conform to Encodable.: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.43/src/swift/stdlib/public/core/Codable.swift, line 3280
struct Article: Encodable {
let tags: [Tag]
let title: String
enum CodingKeys: String, CodingKey {
case tags
case title
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(tags, forKey: .tags)
try container.encode(title, forKey: .title)
}
}
let article = Article(tags: [AuthorTag(value: "Author Tag"), GenreTag(value:"A Genre Tag")], title: "A Title")
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(article)
let jsonString = String(data: jsonData, encoding: .utf8)
这是Codeable.swift的相关部分
guard Element.self is Encodable.Type else {
preconditionFailure("\(type(of: self)) does not conform to Encodable because \(Element.self) does not conform to Encodable.")
}
来源:https
:
//github.com/apple/swift/blob/master/stdlib/public/core/Codable.swift
您的第一个示例无法编译(第二次崩溃)的原因是因为协议不符合自身
–
不是符合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]
before编码,然后将unbox解码:
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")
问题内容: 我设法同时使用JSON和plist编码和解码,但是只能通过直接在特定对象上调用encoding / decode函数来实现。 例如: 这很好,没有问题。 但是,我试图获得一个仅接受协议一致性作为类型并保存该对象的函数。 这将导致以下错误: 无法使用类型为“(可编码)”的参数列表调用“编码” 看一下encoding函数的定义,似乎它应该能够接受,除非是我不知道的某种奇怪的类型。 问题答案
问题内容: 编译错误如下: 类型“ AnyObject”不符合协议“ SequenceType” 这种压力是什么? 谁能帮我很多忙! 问题答案: 苹果在Swift编程语言中指出: for-in循环针对范围,序列,集合或进度中的每个项目执行一组语句。 目前,它仅符合protocol ,因此您无法在其上使用for循环。如果要这样做,则必须执行类似以下操作:
问题内容: Beta 3一切正常,现在出现一个奇怪的错误,而且我不知道如何解决它。尝试了所有类似问题的解决方案。 这是我的代码: 两条标记线都给了我相同的错误: 类型“ String.Index”不符合协议“ IntegerLiteralConvertible” 有人能帮我吗?还是Beta 4有漏洞?谢谢! 问题答案: 在Beta 4中,Swift的String.Index处理再次发生了变化- 现
问题内容: 假设我的数据类型包含一个属性,该属性可以在客户对象中包含任何JSON字典 该属性可以是任何任意的JSON映射对象。 在我可以使用反序列化的JSON(但使用新的Swift 4 协议)转换属性之前,我仍然想不出一种方法。 有谁知道如何在Swift 4中使用Decodable协议实现这一目标? 问题答案: 从我发现的要点中得到一些启发,我为和编写了一些扩展名。您可以在此处找到我的要旨的链接。
问题内容: 可以说我已经创建了这个协议和几个类 现在,我想声明一个数组,其中包含符合此类协议的对象类。我该如何申报?理想情况下,我希望编译器检查数组(在编译时)是否正确填充,而不是自己在运行时(运行时)检查它。 这是我尝试的没有成功的方法:( 这会导致编译错误: ‘任何对象都没有名为’construct’的成员 由于类本身不符合协议(它们的实例符合),因此编写这种方法甚至更糟。 类型“ MyCon
我正在使用Google Protocol Buffers向服务器发送消息。我对如何发送图像与如何接收图像感到困惑。有关详细信息,请参阅下面的代码,但我的问题是: 我需要base64_decode从未经过base64编码的返回字符串吗,因为它是使用char*和size发送的?也许Google Protocol Buffers处理了这个问题,但我在生成的类中找不到任何证据。 我可能在这里找到了答案,但