为什么这个模型不符合 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)
}
}
您好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
becauseArtistData
does not conform toDecodable
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)
}
}