Your opening a somewhat ugly can of worms here. I understand what you try to do, but unfortunately it fails in a number of ways. You can get somewhat close to what you want with the following 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)
}
However you will notice that there are some changes that do have a reason. As a matter of fact you cannot implement the Decodable
method init(from: Decoder) throws
since it is supposed to chain
to the init
method which ... does not really work out for a protocol. I chose instead to implement your favourite dispatcher in the Animal.make
method, but this ended up as a half baked solution as well. Since protocols
are metatypes (probably for a good reason as well) you are not able to call their static methods on the metatype and have to use a concrete one. As the line Dog.make(fromJSON: catData)
shows this looks weird to say the least. It would be better to bake this into a top level function such as
func parseAnimal(from data:Data) {
...
}
but still this looks unsatisfactory in another way since it pollutes the global namespace. Probably still the best we can do with the means available.
Given the ugliness of the dispatcher it seems like a bad idea to have JSON with no direct indication of the type since it makes parsing really hard. However, I do not see a nice way to communicate a subtype in JSON in a way that really makes it easy to parse. Have not done any research on this, but it might be your next try.