为什么这个模型不符合 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'

遇到这种情况应该怎么处理?

节日快乐。

data in Artist 必须是具体类型或限制为 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
}

作为 ,您收到错误:

cannot automatically synthesize Decodable because ArtistData does not conform to 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)
        }
    }
}

这应该首先从根本上解决您的问题。


现在将这个答案扩展得更远一点,正如我所看到的那样,您故意尝试对 IndividualArtist 和 [=24] 的 type 使用常量值=],您需要手动考虑 decoding 部分。否则 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)
    }
}