CoreOffice / XMLCoder

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

Decoding 3rd party Codable data structures #243

Closed kamcma closed 2 years ago

kamcma commented 2 years ago

Was having trouble decoding into IdentifiedArray, a data structure from @pointfreeco. It conforms to Codable. Came up with the following minimal reproduction:

import IdentifiedCollections
import XCTest
import XMLCoder

let sourceXML: String = """
<?xml version="1.0" encoding="utf-8"?>
<team>
  <user>
    <id>1</id>
    <name>Bob</name>
  </user>
  <user>
    <id>2</id>
    <name>Alice</name>
  </user>
</team>
"""

struct User: Decodable, Identifiable {
  let id: Int
  let name: String
}

struct Team: Decodable {
  let users: [User]

  enum CodingKeys: String, CodingKey {
    case users = "user"
  }
}

struct IdentifiableTeam: Decodable {
  let users: IdentifiedArrayOf<User>

  enum CodingKeys: String, CodingKey {
    case users = "user"
  }
}

final class XMLCoderTests: XCTestCase {
  func testDecodeArray() throws {
    let decoder = XMLDecoder()
    guard let data = sourceXML.data(using: .utf8) else { XCTFail(); return }
    let team = try decoder.decode(Team.self, from: data)
    XCTAssertEqual(2, team.users.count)
  }

  func testDecodeIdentifiedArray() throws {
    let decoder = XMLDecoder()
    guard let data = sourceXML.data(using: .utf8) else { XCTFail(); return }
    let team = try decoder.decode(IdentifiableTeam.self, from: data) // ❌ throws
    XCTAssertEqual(2, team.users.count)
  }
}

I would expect both tests to pass. The vanilla array decoding does pass, but the IdentifiedArray decoding throws the following:

testDecodeIdentifiedArray(): failed: caught error: "typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "user", intValue: nil), XMLKey(stringValue: "0", intValue: 0), XMLKey(stringValue: "0", intValue: 0)], debugDescription: "Expected to decode Dictionary<String, Any> but found ChoiceBox instead.", underlyingError: nil))"

Had a little trouble understanding XMLCoder's internals, but did notice that it extends a few standard library data structures to conform to an internal protocol AnySequence. If I change my minimal reproduction like so:

-import XMLCoder
+@testable import XMLCoder

// ...

+extension IdentifiedArray: AnySequence where Element: Identifiable, ID == Element.ID { }

then both tests pass.

Are my findings correct that conformance to this internal protocol are necessary to decode arbitrary data structures from outside the standard library?

I think ideally this would not be the case, and the encoder/decoder implementations would support arbitrary Codable types generally. But I understand the maintainers of the library are not the original authors.

In the mean time, if conformance to AnySequence is required, what do the maintainers think about making it public?

MaxDesiatov commented 2 years ago

@kamcma I've renamed the protocol to XMLDecodableSequence and made it public in #244, does that resolve your issue?

kamcma commented 2 years ago

It does. Thanks.

MaxDesiatov commented 2 years ago

Great, I've tagged 0.14.0 which includes this change. Thanks for reporting the issue!