Argo
Argo copied to clipboard
Decoding subclasses
I'm trying to convert some code to use Argo, and I'm getting stuck where the existing code uses subclasses. I can't get type inference to pass.
If anyone knows how to get this to work, I'd love some help.
Error Messages
error: cannot convert value of type 'Decoded<_>' to specified type 'Decoded<Child>'
let child: Decoded<Child> = json <| "child"
~~~~~^~~~~~~~~~
error: cannot convert value of type 'Decoded<[_]?>' (aka 'Decoded<Optional<Array<_>>>') to specified type 'Decoded<[Child]?>' (aka 'Decoded<Optional<Array<Child>>>')
let children: Decoded<[Child]?> = json <||? "children"
~~~~~^~~~~~~~~~~~~~~
Models
class Parent: Decodable {
let title: String
init(title: String) {
self.title = title
}
typealias DecodedType = Parent
static func decode(_ json: JSON) -> Decoded<Parent> {
return curry(Parent.init)
<^> json <| "title"
}
}
class Child: Parent {
let subtitle: String
init(title: String, subtitle: String) {
self.subtitle = subtitle
super.init(title: title)
}
typealias DecodedType = Child
static func decode(_ json: JSON) -> Decoded<Child> {
return curry(Child.init)
<^> json <| "title"
<*> json <| "subtitle"
}
}
final class Consumer: Decodable {
let child: Child
let children: [Child]?
init(child: Child, children: [Child]?) {
self.child = child
self.children = children
}
static func decode(_ json: JSON) -> Decoded<Consumer> {
// Changing these from Child to Parent makes it work
let child: Decoded<Child> = json <| "child"
let children: Decoded<[Child]?> = json <||? "children"
return curry(self.init)
<^> child
<*> children
}
}
Argo Version
Argo 4.1.2 Xcode 8.3.2
Dependency Manager
Carthage
Does it make a difference if you explicitly redeclare Child
as Decodable
?
Does it make a difference if you explicitly redeclare
Child
asDecodable
?
error: redundant conformance of 'Child' to protocol 'Decodable'
class Child: Parent, Decodable {
^
note: 'Child' inherits conformance to protocol 'Decodable' from superclass here
class Child: Parent, Decodable {
^
Here's my current thinking on this:
- Because classes, in order to truly make this work I think you'd need to declare
class func decode
instead ofstatic
- With that, you'd need to then explicitly mark the version in
Child
asoverride
- Even that still doesn't quite work:
struct Box<T> {
var value: T
}
protocol A {
associatedtype B
static func go() -> Box<B>
}
class Foo: A {
class func go() -> Box<Foo> {
return Box(value: Foo())
}
}
class Bar: Foo {
// error: method does not override any method from its superclass
override class func go() -> Box<Bar> {
return Box(value: Bar())
}
}
print(Foo.go())
print(Bar.go())
In this example, if you replace Box<T>
with Optional<T>
it works. So I think the problem is we can't declare that Decoded
is covariant over T
.
I'm pretty sure that the ambiguity in the example stems from the child decode
method not being recognised as an override of the parent's, and so the compiler considers Parent.decode
and Child.decode
as overloads, rather than the same method.
I could be missing something (wasn't able to test against Argo with your specific example, so trying to reproduce with custom types), but I think that explains what's going on here.
It seams redundant, but does adding (json <| "child") as Decoded<Child>
make any difference?
For now I'm worked around it like this:
struct ChildDecoder {
let child: Child
init(title: String, subtitle: String) {
child = Child(title: title, subtitle: subtitle)
}
static func decode(_ json: JSON) -> Decoded<ChildDecoder> {
return curry(Child.init)
<^> json <| "title"
<*> json <| "subtitle"
}
}
private func decodeChildren(key: String, from json: JSON) -> Decoded<[Child]?> {
let decoded: Decoded<[ChildDecoder]?> = json <||? key
return decoded.map { optional in optional.map { array in array.map { $0.child } } }
}
But I'd love to avoid that if there's a way to make this work.
What about replacing let child: Decoded<Child> = json <| "child"
with let child = Child.decode(json <| "child")
?
does adding
(json <| "child") as Decoded<Child>
make any difference?
Nope.
What about replacing
let child: Decoded<Child> = json <| "child"
withlet child = Child.decode(json <| "child")
?
error: cannot convert call result type 'Decoded<_>' to expected type 'JSON'
let child = Child.decode(json <| "child")
^~
Oh yeah, that'd need to use flatMap
:
json <| "child" >>- Child.decode
Maybe?
json <| "child" >>- Child.decode
That seems to work!
Is there a convenient way to leverage that for the <||?
case?
a bit more complicated (and off the top of my head, so forgive slight syntax issues) but I think you can do that like:
.optional(json <| "children" >>- [Child].decode)
and then obviously you could pull that into a helper function or something if you need to
.optional(json <| "children" >>- [Child].decode)
Sadly, that doesn't work. 😞
'Parent.DecodedType' (aka 'Parent') is not convertible to 'Child'
Sorry I ghosted on this. Messing around with this now, it seems like there's something weird going on with the array stuff, and is unrelated to the optionality of the result:
let child: Decoded<Child?> = .optional(json <| "child" >>- Child.decode)
// ^^ fine
let children: Decoded<[Child]> = json <|| "children" >>- [Child].decode
// error: 'Child' is not convertible to 'Parent.DecodedType' (aka 'Parent')
I'll admit, I'm a little lost on this one. I can't quite see what is happening here.
@mdiep did you ever resolve this?
No, I had to keep using this workaround.