[英]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
becauseArtistData
does not conform toDecodable
无法自动合成
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.现在将这个答案扩展一点,正如我可以看到您试图实现的目标,您故意尝试对
IndividualArtist
和BandArtist
的type
使用常量值,您需要手动考虑解码部分。 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.