你在这里打开了一个有点丑陋的蠕虫罐头。我理解你试图做什么,但不幸的是它在很多方面都失败了。您可以通过以下 Playground 获得一些接近您想要的东西:
import Cocoa
let dogData = """
{
"name": "fleabag",
"age": 3,
"type": "big"
}
""".data(using: .utf8)!
let catData = """
{
"name": "felix",
"age": 2,
"color": "black"
}
""".data(using: .utf8)!
protocol Animal: Codable
{
var name: String { get }
var age: Int { get }
}
struct Dog: Animal
{
let name: String
let age: Int
let type: String
}
struct Cat: Animal
{
let name: String
let age: Int
let color: String
}
do {
let decoder = JSONDecoder()
let dog = try decoder.decode(Dog.self, from: dogData)
print(dog)
let cat = try decoder.decode(Cat.self, from: catData)
print(cat)
}
extension Animal {
static func make(fromJSON data: Data) -> Animal? {
let decoder = JSONDecoder()
do {
let dog = try decoder.decode(Dog.self, from: data)
return dog
} catch {
do {
let cat = try decoder.decode(Cat.self, from: data)
return cat
} catch {
return nil
}
}
}
}
if let animal = Dog.make(fromJSON: dogData) {
print(animal)
}
if let animal2 = Dog.make(fromJSON: catData) {
print(animal2)
}
但是,您会注意到有一些变化是有原因的。事实上,您无法实现该Decodable
方法init(from: Decoder) throws
,因为它应该适用chain
于init
......并不能真正适用于协议的方法。我选择在该Animal.make
方法中实现您最喜欢的调度程序,但这最终也成为了一个半生不熟的解决方案。由于protocols
是元类型(可能也有充分的理由),因此您无法在元类型上调用它们的静态方法,而必须使用具体的方法。正如这条线Dog.make(fromJSON: catData)
所示,至少可以说这看起来很奇怪。最好将其烘焙到顶级功能中,例如
func parseAnimal(from data:Data) {
...
}
但这仍然以另一种方式看起来并不令人满意,因为它污染了全局命名空间。可能仍然是我们可以用可用的手段做的最好的事情。
鉴于调度程序的丑陋,使用没有直接指示类型的 JSON 似乎是个坏主意,因为它使解析变得非常困难。但是,我看不到以真正易于解析的方式在 JSON 中传达子类型的好方法。尚未对此进行任何研究,但这可能是您的下一次尝试。