简体   繁体   English

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

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

Hello Codable experts.您好Codable专家。

I want to decode incoming JSON for artists data.我想为艺术家数据解码传入的 JSON。 Two types of artists exist: individuals and bands.存在两种类型的艺术家:个人和乐队。

Individuals are represented like this:个人是这样表示的:

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

while bands are represented like this:而乐队是这样表示的:

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

I'm trying to model this JSON polymorphism (🤔 not sure this is the right word) in Swift like so:我正在尝试在 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
}

But I get the following error:但我收到以下错误:

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

I also tried a version where the ArtistData protocol was not inheriting Decodable and changing Artist definition to use protocol composition like so:我还尝试了一个版本,其中ArtistData协议没有继承Decodable并更改Artist定义以使用协议组合,如下所示:

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

but I get a similar error:但我收到类似的错误:

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

How should I deal with this kind of scenario?我应该如何处理这种情况?

Happy holiday season.🌲🎅节日快乐。🌲🎅

data in Artist must be a concrete type or a generic constrained to Codable . Artist data必须是具体类型或限制到Codable的泛型。 It can't be a protocol.它不可能是一个协议。

My suggestion is to drop the protocol and declare an enum with associated types.我的建议是放弃协议并声明一个具有关联类型的枚举。

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
}

As Protocol doesn't conform to itself?由于协议不符合自身? , you are getting the error: ,您收到错误消息:

cannot automatically synthesize Decodable because ArtistData does not conform to Decodable无法自动合成Decodable因为ArtistData不符合Decodable

Codable requires all the properties of the conforming type are of concrete type or constrained to Codable by means of generics. Codable要求符合类型的所有属性都是具体类型或通过泛型约束到Codable The type of the properties must be a full fledged type but not Protocol .属性的类型必须是完整的类型,但不是Protocol Hence your second approach doesn't work out as you expected.因此,您的第二种方法并没有像您预期的那样奏效。


Though @vadian already posted an excellent answer , I'm posting another approach which may be more inline with your thought process.虽然@vadian 已经发布了一个很好的答案,但我正在发布另一种可能更符合您的思维过程的方法。


First, the Artist type should reflect the original payload data structure:首先, Artist类型应该反映原始的有效载荷数据结构:

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

Where the Either type is like: 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)
        }
    }
}

This should essentially solve your problem in the first place.这应该首先解决您的问题。


Now to extend this answer a little far, as I can see what you tried to achieve, where you deliberately tried to use constant value for the type of both IndividualArtist and BandArtist , you need to account for the decoding part manually.现在将这个答案扩展一点,正如我可以看到您试图实现的目标,您故意尝试对IndividualArtistBandArtisttype使用常量值,您需要手动考虑解码部分。 Otherwise the values of type properties here will be dumped by the JSON payload at the time of decoding (when the auto synthesized init(from decoder: Decoder) gets called).否则这里的type属性的值将在解码时被JSON有效负载转储(当自动合成的init(from decoder: Decoder)被调用时)。 That essentially means, if the JSON is:这基本上意味着,如果JSON是:

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

the JSONDecoder still will decode this as a BandArtist whereas this should not be the case as far as I understand like the way you are concerned. JSONDecoder仍然会将其解码为BandArtist而据我所知,情况并非如此,就像您所关心的那样。 To tackle this, you need to provide custom implementation of Decodable requirement.为了解决这个问题,您需要提供可Decodable要求的自定义实现。 (@vadian's answer account for this with the nested container ) (@vadian 使用嵌套容器对此进行了回答)

Below is how you can do it from the types themselves:以下是您如何从类型本身做到这一点:

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