XMLCoder
XMLCoder copied to clipboard
Attributes & Elements (Non key Value)
Hi,
I was trying to decode a value which included 2 decodable types:
struct Foo: Codable, DynamicNodeEncoding, DynamicNodeDecoding {
let id: Int?
let parentid: Int?
let name: String?
let count: Int?
let haschildren: Bool?
let foolist: FooList?
enum CodingKeys: String, CodingKey {
case id, parentid, name, count, haschildren
case foolist = ""
}
static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
switch key {
case CodingKeys.foolist:
return .element
default:
return .attribute
}
}
static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
switch key {
case CodingKeys.foolist:
return .element
default:
return .attribute
}
}
}
and
typealias FooList = Array<Foo>
This works absolutely fine unless one of the child elements of Foo (not attributes) is of type FooList.
e.g. Foo can contain children Foo's, which appear as a FooList in the data... here is an example of the data (xml):
<foo name="Monkey" id="1" parentid="0" count="123" haschildren="true">
<foolist>
<foo name="Another Monkey" id="2" parentid="1" count="321" haschildren="false" />
<foo name="Final Monkey" id="3" parentid="1" count="123" haschildren="false" />
</foolist>
</foo>
If I decode a FooList e.g.:
<foolist>
<foo name="Another Monkey" id="2" parentid="1" count="321" haschildren="false" />
<foo name="Final Monkey" id="3" parentid="1" count="123" haschildren="false" />
</foolist>
...by itself, it decodes without any issues, however when I decode a foo by itself e.g.:
<foo name="Monkey" id="1" parentid="0" count="123" haschildren="true" />
That too works fine..
When it comes to a Foo with a child element of type FooList, then nothing is picked up, even with tying various adaptations of the Key, Value Intrinsic variation in the examples..
In the above, the variable called foolist: FooList has been tried as stringValue: String and value: String and value: FooList, also various variations of optional have been tried with those.
What's the best way to get the child elements of a Foo, which is a FooList i.e. array of Foo to decode at the same time?
So in the above example (topmost code), the value for let foolist: FooList? would get populated if there was a foolist child element to a foo, if not, then it doesn't matter, as it's just null or even an Array<Foo> with zero Foo elements
Thanks
Ade
Hey @saintborn, did you try introducing a new intermediate struct that represents FooList?
The overall types would look like this (I'm omitting the details):
struct Foo: Codable {
let foolist: FooList?
// the rest of the properties here
}
struct FooList: Codable {
let items: [Foo]
}
No, but I'll have a look when I can.
Doesn't an Array<Foo> conform to Encodable/Decodable (or Codable if both) if the element Foo confirms to Encodable/Decodable (or Codable if both)?
enum CodingKeys: String, CodingKey {
case id, parentid, name, count, haschildren
case foolist = ""
}
should be
enum CodingKeys: String, CodingKey {
case id, parentid, name, count, haschildren
case foolist
}
You shouldn't set foolist = ""
Removing
typealias FooList = Array<Foo>
Full working model should be
struct Foo: Codable, DynamicNodeEncoding, DynamicNodeDecoding {
let id: Int?
let parentid: Int?
let name: String?
let count: Int?
let haschildren: Bool?
let foolist: FooList?
enum CodingKeys: String, CodingKey {
case id, parentid, name, count, haschildren
case foolist
}
static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
switch key {
case CodingKeys.foolist:
return .element
default:
return .attribute
}
}
static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
switch key {
case CodingKeys.foolist:
return .element
default:
return .attribute
}
}
}
struct FooList: Codable, DynamicNodeEncoding, DynamicNodeDecoding {
var foos:[Foo]
static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
return .element
}
static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
return .element
}
enum CodingKeys: String, CodingKey {
case foos = "foo"
}
}
I have another related use case that I'm struggling to work. My XML looks like
<?xml version="1.0" encoding="utf-8"?>
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
<ERRORCODE>0</ERRORCODE>
<RESULTSET FOUND="1">
<ROW MODID="0" RECORDID="1">
<COL>
<DATA>GJE123456789-207</DATA>
</COL>
<COL>
<DATA>42a0f316-0327-4886-99a9-766fe6ffd07f</DATA>
</COL>
<COL>
<DATA>42a0f316-0327-4886-99a9-766fe6ffd07f</DATA>
</COL>
</ROW>
</RESULTSET>
</FMPXMLRESULT>
And I have types to decode it currently looking like:
struct FMPXMLRESULT: Codable {
let RESULTSET: ShipmentFMResultSet
}
struct ShipmentFMResultSet: Codable {
let ROW: [FMResultRow]
}
struct FMResultRow: Codable, DynamicNodeDecoding {
let recordID: Int
let COL: [FMResultCol]
enum CodingKeys: String, CodingKey {
case recordID = "RECORDID"
case COL
}
static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
guard key.intValue == CodingKeys.recordID.intValue else {
return .elementOrAttribute
}
return .attribute
}
}
struct FMResultCol: Codable {
let DATA: String
}
However the COL property on ROW is always nil. I can read the RECORDID fine, which is great, but then I can't read any data inside the attribute - any idea?
Hi @0xTim, you're comparing intValue of CodingKey, which I think is always zero in this case since this property is supposed to be used for array indices. I've updated nodeDecoding to this and it works for me with your example:
static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
// Compare `stringValue`, not `intValue`, `intValue` is for unkeyed container indices (e.g. arrays)
guard key.stringValue == CodingKeys.recordID.stringValue else {
return .elementOrAttribute
}
return .attribute
}
I agree that the nodeDecoding API seems to be not so ideal at the moment, we have property wrappers support for that in the works in #192.
Ah brilliant, thanks @MaxDesiatov !