簡體   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