CoreOffice / XMLCoder

Easy XML parsing using Codable protocols in Swift
https://coreoffice.github.io/XMLCoder/
MIT License
795 stars 107 forks source link

Specify starting node for decoding #71

Open Eitot opened 5 years ago

Eitot commented 5 years ago

Perhaps I am overlooking this, but I think it would be useful if the starting node could be specified in some way, perhaps via XPath. While trying to implement an RSS feed, I found that I either have to implement the parent node to get to the child nodes I am interested in or write my own init(from decoder: Decoder).

For example:

<?xml version="1.0"?>
<rss version="2.0">
  <channel>
    <title>Title</title>
…
  </channel>
</rss>

To get to the title node, I need to implement a type for the channel node as well:

struct RSSFeed: Decodable {
  let channel: Channel

  struct Channel: Decodable {
    let title: String
  }
}
MaxDesiatov commented 5 years ago

Hi @Eitot, thank you for the suggestions. This somewhat overlaps with #10 as far as I understand. We do have in mind a new API for resolving some of the problems with mapping different structures, but it doesn't cover this specific case. Maybe you could suggest some to way to tie that in? XPath is a very interesting idea, I wonder how difficult it would be to implement something like that 🤔

MaxDesiatov commented 5 years ago

I wonder if we could make CodingKeys handle proper XPath, this would probably need a flag like isXPathEnabled or something on XMLDecoder and XMLEncoder, so that there's no performance impact on types that don't use it.

struct RSSFeed: Decodable {
  let channel: String

  enum CodingKeys: String, CodingKey {
    case channel = "channel/title"
  }
}

CC @regexident as he has a lot of experience with Codable internals and I hope would be able to clarify if this XPath idea makes any sense.

Eitot commented 5 years ago

To be honest, I have no idea how to implement this. JSONDecoder and PropertyListDecoder have the same shortcoming. They require an init(from decoder: Decoder) implementation that does way with the code generation. Depending on the size of the model, it could lead to a lot of boilerplate code. Your suggested solution won’t avoid this either.

I am looking for a solution that keeps the code generation intact, while allowing for a straightforward model declaration. Essentially, I just want to declare this:

struct RSSFeed: Decodable {
  let title: String
}

At the moment I have to implement a init(from decoder: Decoder) implementation like this:

struct RSSFeed: Decodable {
  let title: String

  private enum CodingKeys: String, CodingKey {
      case channel
      case title
  }

  init(from decoder: Decoder) throws {
      let channel = try decoder.container(keyedBy: CodingKeys.self)
      let values = try channel.nestedContainer(keyedBy: CodingKeys.self, forKey: .channel)
      title = try values.decode(String.self, forKey: .title)
  }
}

Bear in mind, this is just a simple example. RSS is of course a lot more extensive than this.

Perhaps it would be enough to add an optional startingNode property to XMLDecoder?

let decoder = XMLDecoder()
decoder.startingNode = "channel" // or "/rss/channel"
let feed = try decoder.decode(RSSFeed.self, from: data)
MaxDesiatov commented 5 years ago

Ok, I see. Thank you for clarifying @Eitot. My initial understanding was that you were looking for a way to "skip" elements in general at any stage during decoding, no just the initial element. Specifying just the starting node does make sense. In your case specifying "channel" would point to a node unambiguously, but for this to work in general case, XPath probably works better overall.

But it looks like a work around does exist if I understand correctly and it's not that problematic at the moment?

Unfortunately, we still have a few higher priority issues, the top one being ordered encoding. If you know there's a lightweight XPath implementation for Swift we could incorporate somehow, please let me know. This would probably make the starting node API much easier to implement.

Eitot commented 5 years ago

Indeed, it is not a problem, just… inelegant. After all, this is how JSONDecoder works too. I’d be happy if this is implemented at some point. 👍

mxcl commented 5 years ago

This doesn't sound worth it, it's not that arduous to define the whole structure.