当前位置: 首页 > 面试题库 >

使用JSONEncoder编码/解码类型符合协议的数组

刁俊人
2023-03-14
问题内容

我正在尝试找到使用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处理了这个问题,但我在生成的类中找不到任何证据。 我可能在这里找到了答案,但