CoreOffice / XMLCoder

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

Mixed choice/non-choice encoding #154

Closed bwetherfield closed 4 years ago

bwetherfield commented 4 years ago

Overview

Fixes bug encountered when encoding structs that hold a mixture of choice-element and non-choice-element (or multiple choice-element) properties.

Example

Given a structure that stores both a choice and non-choice property,

private struct IntOrStringAndDouble: Equatable {
    let intOrString: IntOrString
    let decimal: Double
}

the natural encoding approach (now available) is

extension IntOrStringAndDouble: Encodable {
    enum CodingKeys: String, CodingKey {
        case decimal
    }

    func encode(to encoder: Encoder) {
        try intOrString.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(decimal, forKey: .decimal)
    }
}
The following encode implementation also works

```swift extension IntOrStringAndDouble: Encodable { enum CodingKeys: String, CodingKey { case decimal } func encode(to encoder: Encoder) { var singleValueContainer = encoder.singleValueContainer() try singleValueContainer.encode(intOrString) var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(decimal, forKey: .decimal) } } ```

IntOrString as defined in #119

```swift enum IntOrString: Equatable { case int(Int) case string(String) } extension IntOrString: Encodable { enum CodingKeys: String, CodingKey { case int case string } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case let .int(value): try container.encode(value, forKey: .int) case let .string(value): try container.encode(value, forKey: .string) } } } extension IntOrString.CodingKeys: XMLChoiceCodingKey {} // signifies that `IntOrString` is a choice element ```

Implementation Details

In cases where choice and non-choice elements (or multiple choice elements) co-exist in a keyed container, we merge them into a single XMLKeyedEncodingContainer (wrapping a SharedBox<KeyedBox>). Arrays of choice elements (using XMLUnkeyedEncodingContainer under the hood) are encoded the same way as before, as we do not hit the merging cases. For the array case, we still need the XMLChoiceEncodingContainer structure.

Source Compatibility

This is an additive change.