Skip to content
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

Decoding subclasses #459

Open
mdiep opened this issue May 11, 2017 · 15 comments
Open

Decoding subclasses #459

mdiep opened this issue May 11, 2017 · 15 comments

Comments

@mdiep
Copy link
Contributor

mdiep commented May 11, 2017

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

@sharplet
Copy link

Does it make a difference if you explicitly redeclare Child as Decodable?

@mdiep
Copy link
Contributor Author

mdiep commented May 11, 2017

Does it make a difference if you explicitly redeclare Child as Decodable?

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 {
      ^

@sharplet
Copy link

Here's my current thinking on this:

  1. Because classes, in order to truly make this work I think you'd need to declare class func decode instead of static
  2. With that, you'd need to then explicitly mark the version in Child as override
  3. 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.

@sharplet
Copy link

It seams redundant, but does adding (json <| "child") as Decoded<Child> make any difference?

@mdiep
Copy link
Contributor Author

mdiep commented May 11, 2017

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.

@tonyd256
Copy link
Contributor

What about replacing let child: Decoded<Child> = json <| "child" with let child = Child.decode(json <| "child")?

@mdiep
Copy link
Contributor Author

mdiep commented May 11, 2017

does adding (json <| "child") as Decoded<Child> make any difference?

Nope.

What about replacing let child: Decoded<Child> = json <| "child" with let child = Child.decode(json <| "child")?

error: cannot convert call result type 'Decoded<_>' to expected type 'JSON'
    let child = Child.decode(json <| "child")
                                  ^~

@gfontenot
Copy link
Collaborator

Oh yeah, that'd need to use flatMap:

json <| "child" >>- Child.decode

Maybe?

@mdiep
Copy link
Contributor Author

mdiep commented May 11, 2017

json <| "child" >>- Child.decode

That seems to work!

Is there a convenient way to leverage that for the <||? case?

@gfontenot
Copy link
Collaborator

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)

@gfontenot
Copy link
Collaborator

and then obviously you could pull that into a helper function or something if you need to

@mdiep
Copy link
Contributor Author

mdiep commented May 12, 2017

.optional(json <| "children" >>- [Child].decode)

Sadly, that doesn't work. 😞

'Parent.DecodedType' (aka 'Parent') is not convertible to 'Child'

@gfontenot
Copy link
Collaborator

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.

@gfontenot
Copy link
Collaborator

@mdiep did you ever resolve this?

@mdiep
Copy link
Contributor Author

mdiep commented Jan 11, 2018

No, I had to keep using this workaround.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants