XMLCoder icon indicating copy to clipboard operation
XMLCoder copied to clipboard

No support for DecodableWithConfiguration

Open mflint opened this issue 3 months ago • 0 comments

Problem description

Hello friends,

I'd like to decode some XML using DecodableWithConfiguration, instead of the traditional Decodable protocol. (This is because I want to pass some extra data into a type's initializer, and DecodableWithConfiguration can be more typesafe compared with using a userInfo dictionary)

But I can't see any support for DecodableWithConfiguration in XMLCoder.

Example

This example has a parent type Root (which is Decodable) and a collection of Item children (which are DecodableWithConfiguration). The Root initializer decodes its children with decode(_, forKey:, configuration:).

I've included a JSON message, which can be decoded successfully, and an XML message which causes an error to be thrown:

import Foundation
import XMLCoder

let json = """
{
  "item": [
	{
	  "myInt": 0,
	  "myString": "zero"
	},
	{
	  "myInt": 1,
	  "myString": "one"
	}
  ]
}
"""

let xml = """
<root>
	<item>
		<myInt>0</myInt>
		<myString>zero</myString>
	</item>
	<item>
		<myInt>1</myInt>
		<myString>one</myString>
	</item>
</root>
"""

struct Root: Decodable {
	private enum CodingKeys: CodingKey {
		case item
	}
	
	let item: [Item]
	
	init(from decoder: any Decoder) throws {
		let container = try decoder.container(keyedBy: CodingKeys.self, )
		self.item = try container.decode([Item].self, forKey: .item, configuration: "my configuration")
	}
}

struct Item: DecodableWithConfiguration {
	private enum CodingKeys: CodingKey {
		case myInt
		case myString
	}
	
	let myInt: Int
	let myString: String
	
	init(from decoder: any Decoder, configuration: String) throws {
		// error thrown here, because `decoder` is trying to return a
		// `SingleValueContainer` containing just the `myInt` value
		let container = try decoder.container(keyedBy: CodingKeys.self)
		self.myInt = try container.decode(Int.self, forKey: .myInt)
		self.myString = try container.decode(String.self, forKey: .myString)
	}
}

let jsonDecoder = JSONDecoder()
let xmlDecoder = XMLDecoder()

print("JSON:")
let jsonResult = try jsonDecoder.decode(Root.self, from: Data(json.utf8))
print("\(jsonResult)\n")

print("XML:")
let xmlResult = try xmlDecoder.decode(Root.self, from: Data(xml.utf8))
print("\(xmlResult)\n")

Running this, I get this output:

JSON:
Root(item: [WithConfiguration.Item(myInt: 0, myString: "zero"), WithConfiguration.Item(myInt: 1, myString: "one")])

XML:
Swift/ErrorType.swift:254: Fatal error: Error raised at top level: Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "item", intValue: nil), XMLKey(stringValue: "0", intValue: 0)], debugDescription: "Expected to decode Dictionary<String, Any> but found SingleKeyedBox instead.", underlyingError: nil))

The error occurs because the decoder for Item tries to return a SingleValueContainer instead of the expected KeyedValueContainer.

Offer to contribute

I'm happy to work on this, but have some questions first:

  • is this a contribution that you'd want to receive?
  • am I making a huge complicated job for myself?
  • can you offer any guidance about what the task might involve?

Thank-you! Matthew

mflint avatar Nov 23 '25 09:11 mflint