[英]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)
}
}
}
這應該首先解決您的問題。
現在將這個答案擴展一點,正如我可以看到您試圖實現的目標,您故意嘗試對IndividualArtist
和BandArtist
的type
使用常量值,您需要手動考慮解碼部分。 否則這里的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.