forked from CoreOffice/XMLCoder
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Mixed choice/non-choice encoding (CoreOffice#154)
## 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
- Loading branch information
1 parent
464885d
commit cf3c1f0
Showing
7 changed files
with
224 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// | ||
// IntOrString.swift | ||
// XMLCoderTests | ||
// | ||
// Created by Benjamin Wetherfield on 11/24/19. | ||
// | ||
|
||
import XMLCoder | ||
|
||
internal enum IntOrString: Equatable { | ||
case int(Int) | ||
case string(String) | ||
} | ||
|
||
extension IntOrString: Codable { | ||
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) | ||
} | ||
} | ||
|
||
init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
do { | ||
self = .int(try container.decode(Int.self, forKey: .int)) | ||
} catch { | ||
self = .string(try container.decode(String.self, forKey: .string)) | ||
} | ||
} | ||
} | ||
|
||
extension IntOrString.CodingKeys: XMLChoiceCodingKey {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// | ||
// MixedChoiceAndNonChoiceTests.swift | ||
// XMLCoderTests | ||
// | ||
// Created by Benjamin Wetherfield on 11/24/19. | ||
// | ||
|
||
import XCTest | ||
import XMLCoder | ||
|
||
private struct MixedIntOrStringFirst: Equatable { | ||
let intOrString: IntOrString | ||
let otherValue: String | ||
} | ||
|
||
extension MixedIntOrStringFirst: Encodable { | ||
enum CodingKeys: String, CodingKey { | ||
case otherValue = "other-value" | ||
} | ||
|
||
func encode(to encoder: Encoder) throws { | ||
try intOrString.encode(to: encoder) | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
try container.encode(otherValue, forKey: .otherValue) | ||
} | ||
} | ||
|
||
private struct MixedOtherFirst: Equatable { | ||
let intOrString: IntOrString | ||
let otherValue: String | ||
} | ||
|
||
extension MixedOtherFirst: Encodable { | ||
enum CodingKeys: String, CodingKey { | ||
case otherValue = "other-value" | ||
} | ||
|
||
func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
try container.encode(otherValue, forKey: .otherValue) | ||
try intOrString.encode(to: encoder) | ||
} | ||
} | ||
|
||
private struct MixedEitherSide { | ||
let leading: String | ||
let intOrString: IntOrString | ||
let trailing: String | ||
} | ||
|
||
extension MixedEitherSide: Encodable { | ||
enum CodingKeys: String, CodingKey { | ||
case leading | ||
case trailing | ||
} | ||
|
||
func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
try container.encode(leading, forKey: .leading) | ||
try intOrString.encode(to: encoder) | ||
try container.encode(trailing, forKey: .trailing) | ||
} | ||
} | ||
|
||
private struct TwoChoiceElements { | ||
let first: IntOrString | ||
let second: IntOrString | ||
} | ||
|
||
extension TwoChoiceElements: Encodable { | ||
func encode(to encoder: Encoder) throws { | ||
try first.encode(to: encoder) | ||
try second.encode(to: encoder) | ||
} | ||
} | ||
|
||
class MixedChoiceAndNonChoiceTests: XCTestCase { | ||
func testMixedChoiceFirstEncode() throws { | ||
let first = MixedIntOrStringFirst(intOrString: .int(4), otherValue: "other") | ||
let firstEncoded = try XMLEncoder().encode(first, withRootKey: "container") | ||
let firstExpectedXML = "<container><int>4</int><other-value>other</other-value></container>" | ||
XCTAssertEqual(String(data: firstEncoded, encoding: .utf8), firstExpectedXML) | ||
} | ||
|
||
func testMixedChoiceSecondEncode() throws { | ||
let second = MixedOtherFirst(intOrString: .int(4), otherValue: "other") | ||
let secondEncoded = try XMLEncoder().encode(second, withRootKey: "container") | ||
let secondExpectedXML = "<container><other-value>other</other-value><int>4</int></container>" | ||
XCTAssertEqual(String(data: secondEncoded, encoding: .utf8), secondExpectedXML) | ||
} | ||
|
||
func testMixedChoiceFlankedEncode() throws { | ||
let flanked = MixedEitherSide(leading: "first", intOrString: .string("then"), trailing: "second") | ||
let flankedEncoded = try XMLEncoder().encode(flanked, withRootKey: "container") | ||
let flankedExpectedXML = """ | ||
<container><leading>first</leading><string>then</string><trailing>second</trailing></container> | ||
""" | ||
XCTAssertEqual(String(data: flankedEncoded, encoding: .utf8), flankedExpectedXML) | ||
} | ||
|
||
func testTwoChoiceElementsEncode() throws { | ||
let twoChoiceElements = TwoChoiceElements(first: .int(1), second: .string("one")) | ||
let encoded = try XMLEncoder().encode(twoChoiceElements, withRootKey: "container") | ||
let expectedXML = "<container><int>1</int><string>one</string></container>" | ||
XCTAssertEqual(String(data: encoded, encoding: .utf8), expectedXML) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.