Skip to content

Commit

Permalink
Merge pull request #10249 from itaiferber/master
Browse files Browse the repository at this point in the history
Allow SingleValueContainers to decode collections
  • Loading branch information
itaiferber committed Jun 14, 2017
2 parents cdab4de + 48d183e commit 5daa71c
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 64 deletions.
12 changes: 0 additions & 12 deletions stdlib/public/SDK/Foundation/JSONEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -952,18 +952,6 @@ fileprivate class _JSONDecoder : Decoder {
}

func singleValueContainer() throws -> SingleValueDecodingContainer {
guard !(self.storage.topContainer is [String : Any]) else {
throw DecodingError.typeMismatch(SingleValueDecodingContainer.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get single value decoding container -- found keyed container instead."))
}

guard !(self.storage.topContainer is [Any]) else {
throw DecodingError.typeMismatch(SingleValueDecodingContainer.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get single value decoding container -- found unkeyed container instead."))
}

return self
}
}
Expand Down
12 changes: 0 additions & 12 deletions stdlib/public/SDK/Foundation/PlistEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -733,18 +733,6 @@ fileprivate class _PlistDecoder : Decoder {
}

func singleValueContainer() throws -> SingleValueDecodingContainer {
guard !(self.storage.topContainer is [String : Any]) else {
throw DecodingError.typeMismatch(SingleValueDecodingContainer.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get single value decoding container -- found keyed container instead."))
}

guard !(self.storage.topContainer is [Any]) else {
throw DecodingError.typeMismatch(SingleValueDecodingContainer.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get single value decoding container -- found unkeyed container instead."))
}

return self
}
}
Expand Down
134 changes: 108 additions & 26 deletions test/stdlib/TestJSONEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,19 @@ class TestJSONEncoder : TestJSONEncoderSuper {
func testEncodingTopLevelSingleValueEnum() {
_testEncodeFailure(of: Switch.off)
_testEncodeFailure(of: Switch.on)

_testRoundTrip(of: TopLevelWrapper(Switch.off))
_testRoundTrip(of: TopLevelWrapper(Switch.on))
}

func testEncodingTopLevelSingleValueStruct() {
_testEncodeFailure(of: Timestamp(3141592653))
_testRoundTrip(of: TopLevelWrapper(Timestamp(3141592653)))
}

func testEncodingTopLevelSingleValueClass() {
_testEncodeFailure(of: Counter())
_testRoundTrip(of: TopLevelWrapper(Counter()))
}

// MARK: - Encoding Top-Level Structured Types
Expand All @@ -63,6 +68,18 @@ class TestJSONEncoder : TestJSONEncoderSuper {
_testRoundTrip(of: person, expectedJSON: expectedJSON)
}

func testEncodingTopLevelStructuredSingleStruct() {
// Numbers is a struct which encodes as an array through a single value container.
let numbers = Numbers.testValue
_testRoundTrip(of: numbers)
}

func testEncodingTopLevelStructuredSingleClass() {
// Mapping is a class which encodes as a dictionary through a single value container.
let mapping = Mapping.testValue
_testRoundTrip(of: mapping)
}

func testEncodingTopLevelDeepStructuredType() {
// Company is a type with fields which are Codable themselves.
let company = Company.testValue
Expand Down Expand Up @@ -295,7 +312,7 @@ class TestJSONEncoder : TestJSONEncoderSuper {
encoder.nonConformingFloatEncodingStrategy = nonConformingFloatEncodingStrategy
payload = try encoder.encode(value)
} catch {
expectUnreachable("Failed to encode \(T.self) to JSON.")
expectUnreachable("Failed to encode \(T.self) to JSON: \(error)")
}

if let expectedJSON = json {
Expand All @@ -310,7 +327,7 @@ class TestJSONEncoder : TestJSONEncoderSuper {
let decoded = try decoder.decode(T.self, from: payload)
expectEqual(decoded, value, "\(T.self) did not round-trip to an equal value.")
} catch {
expectUnreachable("Failed to decode \(T.self) from JSON.")
expectUnreachable("Failed to decode \(T.self) from JSON: \(error)")
}
}
}
Expand Down Expand Up @@ -398,7 +415,7 @@ fileprivate enum Switch : Codable {
}

/// A simple timestamp type that encodes as a single Double value.
fileprivate struct Timestamp : Codable {
fileprivate struct Timestamp : Codable, Equatable {
let value: Double

init(_ value: Double) {
Expand All @@ -414,10 +431,14 @@ fileprivate struct Timestamp : Codable {
var container = encoder.singleValueContainer()
try container.encode(self.value)
}

static func ==(_ lhs: Timestamp, _ rhs: Timestamp) -> Bool {
return lhs.value == rhs.value
}
}

/// A simple referential counter type that encodes as a single Int value.
fileprivate final class Counter : Codable {
fileprivate final class Counter : Codable, Equatable {
var count: Int = 0

init() {}
Expand All @@ -431,6 +452,10 @@ fileprivate final class Counter : Codable {
var container = encoder.singleValueContainer()
try container.encode(self.count)
}

static func ==(_ lhs: Counter, _ rhs: Counter) -> Bool {
return lhs === rhs || lhs.count == rhs.count
}
}

// MARK: - Structured Types
Expand Down Expand Up @@ -581,6 +606,62 @@ fileprivate struct DoubleNaNPlaceholder : Codable, Equatable {
}
}

/// A type which encodes as an array directly through a single value container.
struct Numbers : Codable, Equatable {
let values = [4, 8, 15, 16, 23, 42]

init() {}

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let decodedValues = try container.decode([Int].self)
guard decodedValues == values else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "The Numbers are wrong!"))
}
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(values)
}

static func ==(_ lhs: Numbers, _ rhs: Numbers) -> Bool {
return lhs.values == rhs.values
}

static var testValue: Numbers {
return Numbers()
}
}

/// A type which encodes as a dictionary directly through a single value container.
fileprivate final class Mapping : Codable, Equatable {
let values: [String : URL]

init(values: [String : URL]) {
self.values = values
}

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
values = try container.decode([String : URL].self)
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(values)
}

static func ==(_ lhs: Mapping, _ rhs: Mapping) -> Bool {
return lhs === rhs || lhs.values == rhs.values
}

static var testValue: Mapping {
return Mapping(values: ["Apple": URL(string: "http://apple.com")!,
"localhost": URL(string: "http://127.0.0.1")!])
}
}

struct NestedContainersTestType : Encodable {
let testSuperEncoder: Bool

Expand Down Expand Up @@ -675,28 +756,29 @@ struct NestedContainersTestType : Encodable {

#if !FOUNDATION_XCTEST
var JSONEncoderTests = TestSuite("TestJSONEncoder")
JSONEncoderTests.test("testEncodingTopLevelEmptyStruct") { TestJSONEncoder().testEncodingTopLevelEmptyStruct() }
JSONEncoderTests.test("testEncodingTopLevelEmptyClass") { TestJSONEncoder().testEncodingTopLevelEmptyClass() }
JSONEncoderTests.test("testEncodingTopLevelSingleValueEnum") { TestJSONEncoder().testEncodingTopLevelSingleValueEnum() }
JSONEncoderTests.test("testEncodingTopLevelSingleValueStruct") { TestJSONEncoder().testEncodingTopLevelSingleValueStruct() }
JSONEncoderTests.test("testEncodingTopLevelSingleValueClass") { TestJSONEncoder().testEncodingTopLevelSingleValueClass() }
JSONEncoderTests.test("testEncodingTopLevelStructuredStruct") { TestJSONEncoder().testEncodingTopLevelStructuredStruct() }
JSONEncoderTests.test("testEncodingTopLevelStructuredClass") { TestJSONEncoder().testEncodingTopLevelStructuredClass() }
JSONEncoderTests.test("testEncodingTopLevelStructuredClass") { TestJSONEncoder().testEncodingTopLevelStructuredClass() }
JSONEncoderTests.test("testEncodingTopLevelEmptyStruct") { TestJSONEncoder().testEncodingTopLevelEmptyStruct() }
JSONEncoderTests.test("testEncodingTopLevelEmptyClass") { TestJSONEncoder().testEncodingTopLevelEmptyClass() }
JSONEncoderTests.test("testEncodingTopLevelSingleValueEnum") { TestJSONEncoder().testEncodingTopLevelSingleValueEnum() }
JSONEncoderTests.test("testEncodingTopLevelSingleValueStruct") { TestJSONEncoder().testEncodingTopLevelSingleValueStruct() }
JSONEncoderTests.test("testEncodingTopLevelSingleValueClass") { TestJSONEncoder().testEncodingTopLevelSingleValueClass() }
JSONEncoderTests.test("testEncodingTopLevelStructuredStruct") { TestJSONEncoder().testEncodingTopLevelStructuredStruct() }
JSONEncoderTests.test("testEncodingTopLevelStructuredClass") { TestJSONEncoder().testEncodingTopLevelStructuredClass() }
JSONEncoderTests.test("testEncodingTopLevelStructuredSingleStruct") { TestJSONEncoder().testEncodingTopLevelStructuredSingleStruct() }
JSONEncoderTests.test("testEncodingTopLevelStructuredSingleClass") { TestJSONEncoder().testEncodingTopLevelStructuredSingleClass() }
JSONEncoderTests.test("testEncodingTopLevelDeepStructuredType") { TestJSONEncoder().testEncodingTopLevelDeepStructuredType()}
JSONEncoderTests.test("testEncodingDate") { TestJSONEncoder().testEncodingDate() }
JSONEncoderTests.test("testEncodingDateSecondsSince1970") { TestJSONEncoder().testEncodingDateSecondsSince1970() }
JSONEncoderTests.test("testEncodingDateMillisecondsSince1970") { TestJSONEncoder().testEncodingDateMillisecondsSince1970() }
JSONEncoderTests.test("testEncodingDateISO8601") { TestJSONEncoder().testEncodingDateISO8601() }
JSONEncoderTests.test("testEncodingDateFormatted") { TestJSONEncoder().testEncodingDateFormatted() }
JSONEncoderTests.test("testEncodingDateCustom") { TestJSONEncoder().testEncodingDateCustom() }
JSONEncoderTests.test("testEncodingDateCustomEmpty") { TestJSONEncoder().testEncodingDateCustomEmpty() }
JSONEncoderTests.test("testEncodingBase64Data") { TestJSONEncoder().testEncodingBase64Data() }
JSONEncoderTests.test("testEncodingCustomData") { TestJSONEncoder().testEncodingCustomData() }
JSONEncoderTests.test("testEncodingCustomDataEmpty") { TestJSONEncoder().testEncodingCustomDataEmpty() }
JSONEncoderTests.test("testEncodingNonConformingFloats") { TestJSONEncoder().testEncodingNonConformingFloats() }
JSONEncoderTests.test("testEncodingNonConformingFloatStrings") { TestJSONEncoder().testEncodingNonConformingFloatStrings() }
JSONEncoderTests.test("testNestedContainerCodingPaths") { TestJSONEncoder().testNestedContainerCodingPaths() }
JSONEncoderTests.test("testSuperEncoderCodingPaths") { TestJSONEncoder().testSuperEncoderCodingPaths() }
JSONEncoderTests.test("testEncodingDate") { TestJSONEncoder().testEncodingDate() }
JSONEncoderTests.test("testEncodingDateSecondsSince1970") { TestJSONEncoder().testEncodingDateSecondsSince1970() }
JSONEncoderTests.test("testEncodingDateMillisecondsSince1970") { TestJSONEncoder().testEncodingDateMillisecondsSince1970() }
JSONEncoderTests.test("testEncodingDateISO8601") { TestJSONEncoder().testEncodingDateISO8601() }
JSONEncoderTests.test("testEncodingDateFormatted") { TestJSONEncoder().testEncodingDateFormatted() }
JSONEncoderTests.test("testEncodingDateCustom") { TestJSONEncoder().testEncodingDateCustom() }
JSONEncoderTests.test("testEncodingDateCustomEmpty") { TestJSONEncoder().testEncodingDateCustomEmpty() }
JSONEncoderTests.test("testEncodingBase64Data") { TestJSONEncoder().testEncodingBase64Data() }
JSONEncoderTests.test("testEncodingCustomData") { TestJSONEncoder().testEncodingCustomData() }
JSONEncoderTests.test("testEncodingCustomDataEmpty") { TestJSONEncoder().testEncodingCustomDataEmpty() }
JSONEncoderTests.test("testEncodingNonConformingFloats") { TestJSONEncoder().testEncodingNonConformingFloats() }
JSONEncoderTests.test("testEncodingNonConformingFloatStrings") { TestJSONEncoder().testEncodingNonConformingFloatStrings() }
JSONEncoderTests.test("testNestedContainerCodingPaths") { TestJSONEncoder().testNestedContainerCodingPaths() }
JSONEncoderTests.test("testSuperEncoderCodingPaths") { TestJSONEncoder().testSuperEncoderCodingPaths() }
runAllTests()
#endif

0 comments on commit 5daa71c

Please sign in to comment.