繁体   English   中英

为什么这个模型不符合 Decodable? (一个多态的 JSON 圣诞故事)

[英]Why does this model not conform to Decodable? (a polymorphic JSON Christmas tale)

您好Codable专家。

我想为艺术家数据解码传入的 JSON。 存在两种类型的艺术家:个人和乐队。

个人是这样表示的:

{
  "id":"123",
  "data":{
    "type":"individual",
    "firstName":"David",
    "lastName":"Bowie"
  }
}

而乐队是这样表示的:

{
  "id":"124",
  "data":{
    "type":"band",
    "name":"Queen"
  }
}

我正在尝试在 Swift 中模拟这个 JSON多态性(🤔 不确定这是正确的词),如下所示:

import Foundation

struct Artist: Decodable {
    let id: String
    let data: ArtistData
}

enum ArtistType: String, Codable {
    case individual
    case band
}

protocol ArtistData: Decodable {
    var type: ArtistType { get }
}

struct IndividualArtistData: ArtistData, Codable {
    let type = ArtistType.individual
    let firstName: String
    let lastName: String
}

struct BandArtistData: ArtistData, Codable {
    let type = ArtistType.band
    let name: String
}

但我收到以下错误:

Type 'Artist' does not conform to protocol 'Decodable'
cannot automatically synthesize 'Decodable' because 'ArtistData' does not conform to 'Decodable'

我还尝试了一个版本,其中ArtistData协议没有继承Decodable并更改Artist定义以使用协议组合,如下所示:

struct Artist: Decodable {
    let id: String
    let data: ArtistData & Decodable
}

但我收到类似的错误:

cannot automatically synthesize 'Decodable' because 'Decodable & ArtistData' does not conform to 'Decodable'

我应该如何处理这种情况?

节日快乐。🌲🎅

Artist data必须是具体类型或限制到Codable的泛型。 它不可能是一个协议。

我的建议是放弃协议并声明一个具有关联类型的枚举。

enum Artist : Decodable {
    case individual(String, IndividualArtist), band(String, BandArtist)

    private enum CodingKeys : String, CodingKey { case id, data }
    private enum ArtistKeys : String, CodingKey { case type }

    init(from decoder : Decoder) throws
    {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let id = try container.decode(String.self, forKey: .id)
        let nestedContainer = try container.nestedContainer(keyedBy: ArtistKeys.self, forKey: .data)
        let type = try nestedContainer.decode(ArtistType.self, forKey: .type)
        switch type {
            case .individual:
                let individualData = try container.decode(IndividualArtist.self, forKey: .data)
                self = .individual(id, individualData)
            case .band:
                let bandData = try container.decode(BandArtist.self, forKey: .data)
                self = .band(id, bandData)
        }
    }
}

enum ArtistType : String, Decodable {
    case individual
    case band
}

struct IndividualArtist : Decodable {
    let type : ArtistType
    let firstName: String
    let lastName: String
}

struct BandArtist : Decodable {
    let type : ArtistType
    let name: String
}

由于协议不符合自身? ,您收到错误消息:

无法自动合成Decodable因为ArtistData不符合Decodable

Codable要求符合类型的所有属性都是具体类型或通过泛型约束到Codable 属性的类型必须是完整的类型,但不是Protocol 因此,您的第二种方法并没有像您预期的那样奏效。


虽然@vadian 已经发布了一个很好的答案,但我正在发布另一种可能更符合您的思维过程的方法。


首先, Artist类型应该反映原始的有效载荷数据结构:

struct Artist: Codable {
    let id: String
    let data: Either<BandArtist, IndividualArtist>
}

Either类型的地方是这样的:

enum Either<L, R> {
    case left(L)
    case right(R)
}
// moving Codable requirement conformance to extension
extension Either: Codable where L: Codable, R: Codable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            // first try to decode as left type
            self = try .left(container.decode(L.self))
        } catch {
            do {
                // if the decode fails try to decode as right type
                self = try .right(container.decode(R.self))
            } catch {
                // both of the types failed? throw type mismatch error
                throw DecodingError.typeMismatch(Either.self,
                                   .init(codingPath: decoder.codingPath,
                                         debugDescription: "Expected either \(L.self) or \(R.self)",
                                         underlyingError: error))
            }
        }
    }    

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case let .left(left):
            try container.encode(left)
        case let .right(right):
            try container.encode(right)
        }
    }
}

这应该首先解决您的问题。


现在将这个答案扩展一点,正如我可以看到您试图实现的目标,您故意尝试对IndividualArtistBandArtisttype使用常量值,您需要手动考虑解码部分。 否则这里的type属性的值将在解码时被JSON有效负载转储(当自动合成的init(from decoder: Decoder)被调用时)。 这基本上意味着,如果JSON是:

{
  "id":"123",
  "data":{
    "type":"individual",
    "name":"Queen"
  }
}

JSONDecoder仍然会将其解码为BandArtist而据我所知,情况并非如此,就像您所关心的那样。 为了解决这个问题,您需要提供可Decodable要求的自定义实现。 (@vadian 使用嵌套容器对此进行了回答)

以下是您如何从类型本身做到这一点:

struct IndividualArtist: Codable {
    let type = ArtistType.individual
    let firstName: String
    let lastName: String
}
extension IndividualArtist {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let type = try container.decode(ArtistType.self, forKey: .type)
        guard self.type == type else {
            throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Payload doesn't match the expected value"))
        }
        firstName = try container.decode(String.self, forKey: .firstName)
        lastName = try container.decode(String.self, forKey: .lastName)
    }
}

struct BandArtist: Codable {
    let type = ArtistType.band
    let name: String
}
extension BandArtist {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let type = try container.decode(ArtistType.self, forKey: .type)
        guard self.type == type else {
            throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Payload doesn't match the expected value"))
        }
        name = try container.decode(String.self, forKey: .name)
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM