Skip to content

Commit 48d183e

Browse files
author
Itai Ferber
committed
Allow SingleValueContainers to decode collections
SingleValueDecondingContainers in JSON and Plist previously held the assertion that attempting to decode an array or dictionary from them was a type mismatch (since those represented unkeyed and keyed containers, respectively). This assertion is no longer true, though, since encode<T : Encodable>(_:) and decode<T : Decodable>(_:) allow you to do just that. This lifts the assertion and adds unit tests to both implementations to ensure this works. (Addresses https://bugs.swift.org/browse/SR-5089)
1 parent 4b0597a commit 48d183e

File tree

4 files changed

+235
-64
lines changed

4 files changed

+235
-64
lines changed

stdlib/public/SDK/Foundation/JSONEncoder.swift

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -952,18 +952,6 @@ fileprivate class _JSONDecoder : Decoder {
952952
}
953953

954954
func singleValueContainer() throws -> SingleValueDecodingContainer {
955-
guard !(self.storage.topContainer is [String : Any]) else {
956-
throw DecodingError.typeMismatch(SingleValueDecodingContainer.self,
957-
DecodingError.Context(codingPath: self.codingPath,
958-
debugDescription: "Cannot get single value decoding container -- found keyed container instead."))
959-
}
960-
961-
guard !(self.storage.topContainer is [Any]) else {
962-
throw DecodingError.typeMismatch(SingleValueDecodingContainer.self,
963-
DecodingError.Context(codingPath: self.codingPath,
964-
debugDescription: "Cannot get single value decoding container -- found unkeyed container instead."))
965-
}
966-
967955
return self
968956
}
969957
}

stdlib/public/SDK/Foundation/PlistEncoder.swift

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -733,18 +733,6 @@ fileprivate class _PlistDecoder : Decoder {
733733
}
734734

735735
func singleValueContainer() throws -> SingleValueDecodingContainer {
736-
guard !(self.storage.topContainer is [String : Any]) else {
737-
throw DecodingError.typeMismatch(SingleValueDecodingContainer.self,
738-
DecodingError.Context(codingPath: self.codingPath,
739-
debugDescription: "Cannot get single value decoding container -- found keyed container instead."))
740-
}
741-
742-
guard !(self.storage.topContainer is [Any]) else {
743-
throw DecodingError.typeMismatch(SingleValueDecodingContainer.self,
744-
DecodingError.Context(codingPath: self.codingPath,
745-
debugDescription: "Cannot get single value decoding container -- found unkeyed container instead."))
746-
}
747-
748736
return self
749737
}
750738
}

test/stdlib/TestJSONEncoder.swift

Lines changed: 108 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,19 @@ class TestJSONEncoder : TestJSONEncoderSuper {
3939
func testEncodingTopLevelSingleValueEnum() {
4040
_testEncodeFailure(of: Switch.off)
4141
_testEncodeFailure(of: Switch.on)
42+
43+
_testRoundTrip(of: TopLevelWrapper(Switch.off))
44+
_testRoundTrip(of: TopLevelWrapper(Switch.on))
4245
}
4346

4447
func testEncodingTopLevelSingleValueStruct() {
4548
_testEncodeFailure(of: Timestamp(3141592653))
49+
_testRoundTrip(of: TopLevelWrapper(Timestamp(3141592653)))
4650
}
4751

4852
func testEncodingTopLevelSingleValueClass() {
4953
_testEncodeFailure(of: Counter())
54+
_testRoundTrip(of: TopLevelWrapper(Counter()))
5055
}
5156

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

71+
func testEncodingTopLevelStructuredSingleStruct() {
72+
// Numbers is a struct which encodes as an array through a single value container.
73+
let numbers = Numbers.testValue
74+
_testRoundTrip(of: numbers)
75+
}
76+
77+
func testEncodingTopLevelStructuredSingleClass() {
78+
// Mapping is a class which encodes as a dictionary through a single value container.
79+
let mapping = Mapping.testValue
80+
_testRoundTrip(of: mapping)
81+
}
82+
6683
func testEncodingTopLevelDeepStructuredType() {
6784
// Company is a type with fields which are Codable themselves.
6885
let company = Company.testValue
@@ -295,7 +312,7 @@ class TestJSONEncoder : TestJSONEncoderSuper {
295312
encoder.nonConformingFloatEncodingStrategy = nonConformingFloatEncodingStrategy
296313
payload = try encoder.encode(value)
297314
} catch {
298-
expectUnreachable("Failed to encode \(T.self) to JSON.")
315+
expectUnreachable("Failed to encode \(T.self) to JSON: \(error)")
299316
}
300317

301318
if let expectedJSON = json {
@@ -310,7 +327,7 @@ class TestJSONEncoder : TestJSONEncoderSuper {
310327
let decoded = try decoder.decode(T.self, from: payload)
311328
expectEqual(decoded, value, "\(T.self) did not round-trip to an equal value.")
312329
} catch {
313-
expectUnreachable("Failed to decode \(T.self) from JSON.")
330+
expectUnreachable("Failed to decode \(T.self) from JSON: \(error)")
314331
}
315332
}
316333
}
@@ -398,7 +415,7 @@ fileprivate enum Switch : Codable {
398415
}
399416

400417
/// A simple timestamp type that encodes as a single Double value.
401-
fileprivate struct Timestamp : Codable {
418+
fileprivate struct Timestamp : Codable, Equatable {
402419
let value: Double
403420

404421
init(_ value: Double) {
@@ -414,10 +431,14 @@ fileprivate struct Timestamp : Codable {
414431
var container = encoder.singleValueContainer()
415432
try container.encode(self.value)
416433
}
434+
435+
static func ==(_ lhs: Timestamp, _ rhs: Timestamp) -> Bool {
436+
return lhs.value == rhs.value
437+
}
417438
}
418439

419440
/// A simple referential counter type that encodes as a single Int value.
420-
fileprivate final class Counter : Codable {
441+
fileprivate final class Counter : Codable, Equatable {
421442
var count: Int = 0
422443

423444
init() {}
@@ -431,6 +452,10 @@ fileprivate final class Counter : Codable {
431452
var container = encoder.singleValueContainer()
432453
try container.encode(self.count)
433454
}
455+
456+
static func ==(_ lhs: Counter, _ rhs: Counter) -> Bool {
457+
return lhs === rhs || lhs.count == rhs.count
458+
}
434459
}
435460

436461
// MARK: - Structured Types
@@ -581,6 +606,62 @@ fileprivate struct DoubleNaNPlaceholder : Codable, Equatable {
581606
}
582607
}
583608

609+
/// A type which encodes as an array directly through a single value container.
610+
struct Numbers : Codable, Equatable {
611+
let values = [4, 8, 15, 16, 23, 42]
612+
613+
init() {}
614+
615+
init(from decoder: Decoder) throws {
616+
let container = try decoder.singleValueContainer()
617+
let decodedValues = try container.decode([Int].self)
618+
guard decodedValues == values else {
619+
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "The Numbers are wrong!"))
620+
}
621+
}
622+
623+
func encode(to encoder: Encoder) throws {
624+
var container = encoder.singleValueContainer()
625+
try container.encode(values)
626+
}
627+
628+
static func ==(_ lhs: Numbers, _ rhs: Numbers) -> Bool {
629+
return lhs.values == rhs.values
630+
}
631+
632+
static var testValue: Numbers {
633+
return Numbers()
634+
}
635+
}
636+
637+
/// A type which encodes as a dictionary directly through a single value container.
638+
fileprivate final class Mapping : Codable, Equatable {
639+
let values: [String : URL]
640+
641+
init(values: [String : URL]) {
642+
self.values = values
643+
}
644+
645+
init(from decoder: Decoder) throws {
646+
let container = try decoder.singleValueContainer()
647+
values = try container.decode([String : URL].self)
648+
}
649+
650+
func encode(to encoder: Encoder) throws {
651+
var container = encoder.singleValueContainer()
652+
try container.encode(values)
653+
}
654+
655+
static func ==(_ lhs: Mapping, _ rhs: Mapping) -> Bool {
656+
return lhs === rhs || lhs.values == rhs.values
657+
}
658+
659+
static var testValue: Mapping {
660+
return Mapping(values: ["Apple": URL(string: "http://apple.com")!,
661+
"localhost": URL(string: "http://127.0.0.1")!])
662+
}
663+
}
664+
584665
struct NestedContainersTestType : Encodable {
585666
let testSuperEncoder: Bool
586667

@@ -675,28 +756,29 @@ struct NestedContainersTestType : Encodable {
675756

676757
#if !FOUNDATION_XCTEST
677758
var JSONEncoderTests = TestSuite("TestJSONEncoder")
678-
JSONEncoderTests.test("testEncodingTopLevelEmptyStruct") { TestJSONEncoder().testEncodingTopLevelEmptyStruct() }
679-
JSONEncoderTests.test("testEncodingTopLevelEmptyClass") { TestJSONEncoder().testEncodingTopLevelEmptyClass() }
680-
JSONEncoderTests.test("testEncodingTopLevelSingleValueEnum") { TestJSONEncoder().testEncodingTopLevelSingleValueEnum() }
681-
JSONEncoderTests.test("testEncodingTopLevelSingleValueStruct") { TestJSONEncoder().testEncodingTopLevelSingleValueStruct() }
682-
JSONEncoderTests.test("testEncodingTopLevelSingleValueClass") { TestJSONEncoder().testEncodingTopLevelSingleValueClass() }
683-
JSONEncoderTests.test("testEncodingTopLevelStructuredStruct") { TestJSONEncoder().testEncodingTopLevelStructuredStruct() }
684-
JSONEncoderTests.test("testEncodingTopLevelStructuredClass") { TestJSONEncoder().testEncodingTopLevelStructuredClass() }
685-
JSONEncoderTests.test("testEncodingTopLevelStructuredClass") { TestJSONEncoder().testEncodingTopLevelStructuredClass() }
759+
JSONEncoderTests.test("testEncodingTopLevelEmptyStruct") { TestJSONEncoder().testEncodingTopLevelEmptyStruct() }
760+
JSONEncoderTests.test("testEncodingTopLevelEmptyClass") { TestJSONEncoder().testEncodingTopLevelEmptyClass() }
761+
JSONEncoderTests.test("testEncodingTopLevelSingleValueEnum") { TestJSONEncoder().testEncodingTopLevelSingleValueEnum() }
762+
JSONEncoderTests.test("testEncodingTopLevelSingleValueStruct") { TestJSONEncoder().testEncodingTopLevelSingleValueStruct() }
763+
JSONEncoderTests.test("testEncodingTopLevelSingleValueClass") { TestJSONEncoder().testEncodingTopLevelSingleValueClass() }
764+
JSONEncoderTests.test("testEncodingTopLevelStructuredStruct") { TestJSONEncoder().testEncodingTopLevelStructuredStruct() }
765+
JSONEncoderTests.test("testEncodingTopLevelStructuredClass") { TestJSONEncoder().testEncodingTopLevelStructuredClass() }
766+
JSONEncoderTests.test("testEncodingTopLevelStructuredSingleStruct") { TestJSONEncoder().testEncodingTopLevelStructuredSingleStruct() }
767+
JSONEncoderTests.test("testEncodingTopLevelStructuredSingleClass") { TestJSONEncoder().testEncodingTopLevelStructuredSingleClass() }
686768
JSONEncoderTests.test("testEncodingTopLevelDeepStructuredType") { TestJSONEncoder().testEncodingTopLevelDeepStructuredType()}
687-
JSONEncoderTests.test("testEncodingDate") { TestJSONEncoder().testEncodingDate() }
688-
JSONEncoderTests.test("testEncodingDateSecondsSince1970") { TestJSONEncoder().testEncodingDateSecondsSince1970() }
689-
JSONEncoderTests.test("testEncodingDateMillisecondsSince1970") { TestJSONEncoder().testEncodingDateMillisecondsSince1970() }
690-
JSONEncoderTests.test("testEncodingDateISO8601") { TestJSONEncoder().testEncodingDateISO8601() }
691-
JSONEncoderTests.test("testEncodingDateFormatted") { TestJSONEncoder().testEncodingDateFormatted() }
692-
JSONEncoderTests.test("testEncodingDateCustom") { TestJSONEncoder().testEncodingDateCustom() }
693-
JSONEncoderTests.test("testEncodingDateCustomEmpty") { TestJSONEncoder().testEncodingDateCustomEmpty() }
694-
JSONEncoderTests.test("testEncodingBase64Data") { TestJSONEncoder().testEncodingBase64Data() }
695-
JSONEncoderTests.test("testEncodingCustomData") { TestJSONEncoder().testEncodingCustomData() }
696-
JSONEncoderTests.test("testEncodingCustomDataEmpty") { TestJSONEncoder().testEncodingCustomDataEmpty() }
697-
JSONEncoderTests.test("testEncodingNonConformingFloats") { TestJSONEncoder().testEncodingNonConformingFloats() }
698-
JSONEncoderTests.test("testEncodingNonConformingFloatStrings") { TestJSONEncoder().testEncodingNonConformingFloatStrings() }
699-
JSONEncoderTests.test("testNestedContainerCodingPaths") { TestJSONEncoder().testNestedContainerCodingPaths() }
700-
JSONEncoderTests.test("testSuperEncoderCodingPaths") { TestJSONEncoder().testSuperEncoderCodingPaths() }
769+
JSONEncoderTests.test("testEncodingDate") { TestJSONEncoder().testEncodingDate() }
770+
JSONEncoderTests.test("testEncodingDateSecondsSince1970") { TestJSONEncoder().testEncodingDateSecondsSince1970() }
771+
JSONEncoderTests.test("testEncodingDateMillisecondsSince1970") { TestJSONEncoder().testEncodingDateMillisecondsSince1970() }
772+
JSONEncoderTests.test("testEncodingDateISO8601") { TestJSONEncoder().testEncodingDateISO8601() }
773+
JSONEncoderTests.test("testEncodingDateFormatted") { TestJSONEncoder().testEncodingDateFormatted() }
774+
JSONEncoderTests.test("testEncodingDateCustom") { TestJSONEncoder().testEncodingDateCustom() }
775+
JSONEncoderTests.test("testEncodingDateCustomEmpty") { TestJSONEncoder().testEncodingDateCustomEmpty() }
776+
JSONEncoderTests.test("testEncodingBase64Data") { TestJSONEncoder().testEncodingBase64Data() }
777+
JSONEncoderTests.test("testEncodingCustomData") { TestJSONEncoder().testEncodingCustomData() }
778+
JSONEncoderTests.test("testEncodingCustomDataEmpty") { TestJSONEncoder().testEncodingCustomDataEmpty() }
779+
JSONEncoderTests.test("testEncodingNonConformingFloats") { TestJSONEncoder().testEncodingNonConformingFloats() }
780+
JSONEncoderTests.test("testEncodingNonConformingFloatStrings") { TestJSONEncoder().testEncodingNonConformingFloatStrings() }
781+
JSONEncoderTests.test("testNestedContainerCodingPaths") { TestJSONEncoder().testNestedContainerCodingPaths() }
782+
JSONEncoderTests.test("testSuperEncoderCodingPaths") { TestJSONEncoder().testSuperEncoderCodingPaths() }
701783
runAllTests()
702784
#endif

0 commit comments

Comments
 (0)