-
Notifications
You must be signed in to change notification settings - Fork 112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mixed choice/non-choice encoding #154
Mixed choice/non-choice encoding #154
Conversation
// We haven't yet pushed a container at this level; do so here. | ||
topContainer = storage.pushChoiceContainer() | ||
let container = XMLChoiceEncodingContainer<Key>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
likewise here
// We haven't yet pushed a container at this level; do so here. | ||
topContainer = storage.pushKeyedContainer() | ||
let container = XMLKeyedEncodingContainer<Key>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the comment above seems to be outdated now
Attempt to push new (single element) keyed encoding container when already \ | ||
previously encoded at this path. | ||
Attempt to push new keyed encoding container when already previously encoded \ | ||
at this path. | ||
""" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this switch
seems to be a duplicate of switch
in public func keyedContainer
modified above, could it be generalized somehow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good point!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing, many thanks!
Mirrors #154 Fixes bugs 1) Decoding multiple choice elements in the same Keyed Container. 2) Decoding choice elements after decoding regular keyed elements in the same container. Case 1 refers to decoding implementations of the form: ```swift init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) otherValue = try container.decode(String.self, forKey: .otherValue) intOrString = try IntOrString(from: decoder) } ``` where `IntOrString` is a choice coding element `IntOrString` defined as follows: ```swift enum IntOrString { case int(Int) case string(String) } extension IntOrString: Decodable { enum CodingKeys: String, CodingKey { case int case string } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if container.contains(.int) { self = .int(try container.decode(Int.self, forKey: .int)) } else { self = .string(try container.decode(String.self, forKey: .string)) } } } extension IntOrString.CodingKeys: XMLChoiceCodingKey {} // signifies that `IntOrString` is a choice element ``` Case 2 refers to decoding implementations of the following form: ```swift init(from decoder: Decoder) throws { self.first = try IntOrString(from: decoder) self.second = try AlternateIntOrString(from: decoder) } ``` Where both `first: IntOrString` and `second: AlternateIntOrString` are choice elements. `AlternateIntOrString` defined as follows: ```swift private enum AlternateIntOrString { case alternateInt(Int) case alternateString(String) } extension AlternateIntOrString: Decodable { enum CodingKeys: String, CodingKey { case alternateInt = "alternate-int" case alternateString = "alternate-string" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if container.contains(.alternateInt) { self = .alternateInt(try container.decode(Int.self, forKey: .alternateInt)) } else { self = .alternateString(try container.decode(String.self, forKey: .alternateString)) } } } extension AlternateIntOrString.CodingKeys: XMLChoiceCodingKey {} // signifies that `AlternateIntOrString` is a choice element ``` * Add tests * Fix failing tests
## 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, ```swift private struct IntOrStringAndDouble: Equatable { let intOrString: IntOrString let decimal: Double } ``` the natural encoding approach (now available) is ```swift 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 CoreOffice#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. * Add breaking case * Add choice and keyed merging encode functionality * Refactor * Fix commented code * Fix misnamed file * Fix xcode project * Fix precondition catch * Use switch syntax * Add multiple choice element case * Add explicit types in KeyedBox initialization * Add explicitly empty parameter to KeyedBox initializer * Use more concise type inference * Unify switch syntax * Cut down code duplication * Fix formatting
Mirrors CoreOffice#154 Fixes bugs 1) Decoding multiple choice elements in the same Keyed Container. 2) Decoding choice elements after decoding regular keyed elements in the same container. Case 1 refers to decoding implementations of the form: ```swift init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) otherValue = try container.decode(String.self, forKey: .otherValue) intOrString = try IntOrString(from: decoder) } ``` where `IntOrString` is a choice coding element `IntOrString` defined as follows: ```swift enum IntOrString { case int(Int) case string(String) } extension IntOrString: Decodable { enum CodingKeys: String, CodingKey { case int case string } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if container.contains(.int) { self = .int(try container.decode(Int.self, forKey: .int)) } else { self = .string(try container.decode(String.self, forKey: .string)) } } } extension IntOrString.CodingKeys: XMLChoiceCodingKey {} // signifies that `IntOrString` is a choice element ``` Case 2 refers to decoding implementations of the following form: ```swift init(from decoder: Decoder) throws { self.first = try IntOrString(from: decoder) self.second = try AlternateIntOrString(from: decoder) } ``` Where both `first: IntOrString` and `second: AlternateIntOrString` are choice elements. `AlternateIntOrString` defined as follows: ```swift private enum AlternateIntOrString { case alternateInt(Int) case alternateString(String) } extension AlternateIntOrString: Decodable { enum CodingKeys: String, CodingKey { case alternateInt = "alternate-int" case alternateString = "alternate-string" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if container.contains(.alternateInt) { self = .alternateInt(try container.decode(Int.self, forKey: .alternateInt)) } else { self = .alternateString(try container.decode(String.self, forKey: .alternateString)) } } } extension AlternateIntOrString.CodingKeys: XMLChoiceCodingKey {} // signifies that `AlternateIntOrString` is a choice element ``` * Add tests * Fix failing tests
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,
the natural encoding approach (now available) is
The following
encode
implementation also worksIntOrString
as defined in #119Implementation 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 aSharedBox<KeyedBox>
). Arrays of choice elements (usingXMLUnkeyedEncodingContainer
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 theXMLChoiceEncodingContainer
structure.Source Compatibility
This is an additive change.