New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Is there any way to decode an array of different types? #325
Comments
I realized I can do something like this:
Is this the most appropriate thing to do in this scenario? |
I guess this won't actually work because the Swift compiler can't convert |
@maxschmeling I think you'd need to use type erasure to allow that. This article by @rnapier might help: http://robnapier.net/erasure |
Thanks @paulyoung |
I don't think you'll need to go as far as type erasure, instead you should be able to use class Animal {
let name: String
init(name: String) {
self.name = name
}
}
extension Animal: Decodable {
static func decode(j: JSON) -> Decoded<Animal> {
return Cat.decode(j) <|> Dog.decode(j)
}
}
final class Cat: Animal {
let remainingLives: Int
init(name: String, remainingLives: Int) {
self. remainingLives = remainingLives
super.init(name: name)
}
}
extension Cat { // Note that I'm _not_ making Cat decodable
static func decode(j: JSON) -> Decoded<Animal> {
let cat = curry(self.init)
<^> j <| "name"
<*> j <| "remainingLives"
return cat as Decoded<Animal> // This might not be needed, I might try just returning the expression above first
}
}
final class Dog: Animal {
let trained: Bool
init(name: String, trained: Bool) {
self.trained = trained
super.init(name: name)
}
}
extension Dog { // Note that I'm _not_ making Dog decodable
static func decode(j: JSON) -> Decoded<Animal> {
let dog = curry(self.init)
<^> j <| "name"
<*> j <| "trained"
return dog as Decoded<Animal> // This might not be needed, I might try just returning the expression above first
}
} Since the types for the func validateType(expectedType: String, json: JSON) -> Bool {
let type: String? = (json <| "type").value
return type == expectedType
}
// later, in Dog.decode:
guard validateType("dog") else { return .typeMismatch("dog", actual: j) } |
@gfontenot maybe I'm doing something wrong, but the compiler says:
on |
I'm also having trouble getting the type erasure working, but still working on it. Trying to wrap my head around how it would work in this scenario. |
Sorry, I had spiked this code out here on GitHub. I was able to get this to compile by:
The resulting code (that compiles for me): class Animal {
let name: String
init(name: String) {
self.name = name
}
}
extension Animal: Decodable {
static func decode(j: JSON) -> Decoded<Animal> {
return decodeCat(j) <|> decodeDog(j)
}
}
final class Cat: Animal {
let remainingLives: Int
init(name: String, remainingLives: Int) {
self.remainingLives = remainingLives
super.init(name: name)
}
}
func decodeCat(j: JSON) -> Decoded<Animal> {
return curry(Cat.init)
<^> j <| "name"
<*> j <| "remainingLives"
}
final class Dog: Animal {
let trained: Bool
init(name: String, trained: Bool) {
self.trained = trained
super.init(name: name)
}
}
func decodeDog(j: JSON) -> Decoded<Animal> {
return curry(Dog.init)
<^> j <| "name"
<*> j <| "trained"
} |
Type erasure really shouldn't be needed here, because we can use covariance to return |
@gfontenot the compiler errors were making me think that isn't possible. Working on attempting the latest code sample. I appreciate the help. |
I was able to convert this into my real-world code and it's compiling. Will do some testing to make sure it's all working, but it appears to do what I need. I really appreciate the help. |
@maxschmeling Glad it's working for you! Don't hesitate to re-open if you need more help or if you have any other questions. |
@gfontenot just wanted to follow up and say that once I added in the |
how can I go about implementing this with a fairly large model? you know, the type where we have to split curry in intermediate vars to avoid "Expression too complex" errors. func decodeCar(j: JSON) -> Decoded<AdDetailModel> {
let a = curry(CarAdDetailModel.init)
<^> j <| "ad_id"
<*> j <| "phone"
<*> j <| "city_name"
<*> j <| "city_area"
<*> j <| "seller_comments"
<*> j <| "price"
<*> j <| "url_slug"
<*> j <|| "pictures"
<*> j <| "is_featured"
<*> j <| "ad_listing_id"
return a
<*> (j <| "last_updated" >>- toNSDate(""))
<*> j <| "featured_request"
<*> j <| "ad_saved"
<*> j <| "make"
<*> j <| "model"
<*> j <| "version"
} There is a return type mismatch compiler error |
@hunaid-hassan-confiz I'm not sure what you're asking. Can I see your model? Also probably the implementation of |
Nevermind. My model is unusually large. 25+ properties. I was having trouble getting rid of the compiler error "Expression too complex", which I managed to somehow but then having to write an initialiser for every class explicitly was too much of work also Argo hits its limit at 15+ properties after which the compiler error just doesn't go away whatever you do. So I am planning to move to alternative. I haven't decided which though |
To be clear, Argo has zero limits on the number of properties. I believe what you're hitting is the limitations (imposed by the compiler) on the number of TBH, I constantly question the design of models that have that many properties. I don't know the domain you're working in at all, but I'd highly suspect that you could refactor into multiple objects which might improve the overall design as well as make it easier to use with Argo. |
You are right. I am not very happy with the current design of the models. A lot of those properties can be grouped together in smaller structs but its a legacy system I have to work with. |
I have JSON that is analogous to this:
I would love to have an
Animal
type with aname
property and then a subclass forCat
with aremainingLives
property andDog
with atrained
property.It doesn't appear to be possible with
Argo
. Am I missing anything? If not, is this something that you think might fit intoArgo
? I would be happy to work on it with a little guidance. (I'm very new to Swift)The text was updated successfully, but these errors were encountered: