Skip to content

Commit 0ba863e

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 36e1871 commit 0ba863e

File tree

4 files changed

+215
-42
lines changed

4 files changed

+215
-42
lines changed

stdlib/public/SDK/Foundation/JSONEncoder.swift

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

964964
func singleValueContainer() throws -> SingleValueDecodingContainer {
965-
guard !(self.storage.topContainer is [String : Any]) else {
966-
throw DecodingError.typeMismatch(SingleValueDecodingContainer.self,
967-
DecodingError.Context(codingPath: self.codingPath,
968-
debugDescription: "Cannot get single value decoding container -- found keyed container instead."))
969-
}
970-
971-
guard !(self.storage.topContainer is [Any]) else {
972-
throw DecodingError.typeMismatch(SingleValueDecodingContainer.self,
973-
DecodingError.Context(codingPath: self.codingPath,
974-
debugDescription: "Cannot get single value decoding container -- found unkeyed container instead."))
975-
}
976-
977965
return self
978966
}
979967
}

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: 88 additions & 4 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
@@ -326,7 +343,7 @@ class TestJSONEncoder : TestJSONEncoderSuper {
326343
encoder.nonConformingFloatEncodingStrategy = nonConformingFloatEncodingStrategy
327344
payload = try encoder.encode(value)
328345
} catch {
329-
expectUnreachable("Failed to encode \(T.self) to JSON.")
346+
expectUnreachable("Failed to encode \(T.self) to JSON: \(error)")
330347
}
331348

332349
if let expectedJSON = json {
@@ -341,7 +358,7 @@ class TestJSONEncoder : TestJSONEncoderSuper {
341358
let decoded = try decoder.decode(T.self, from: payload)
342359
expectEqual(decoded, value, "\(T.self) did not round-trip to an equal value.")
343360
} catch {
344-
expectUnreachable("Failed to decode \(T.self) from JSON.")
361+
expectUnreachable("Failed to decode \(T.self) from JSON: \(error)")
345362
}
346363
}
347364
}
@@ -429,7 +446,7 @@ fileprivate enum Switch : Codable {
429446
}
430447

431448
/// A simple timestamp type that encodes as a single Double value.
432-
fileprivate struct Timestamp : Codable {
449+
fileprivate struct Timestamp : Codable, Equatable {
433450
let value: Double
434451

435452
init(_ value: Double) {
@@ -445,10 +462,14 @@ fileprivate struct Timestamp : Codable {
445462
var container = encoder.singleValueContainer()
446463
try container.encode(self.value)
447464
}
465+
466+
static func ==(_ lhs: Timestamp, _ rhs: Timestamp) -> Bool {
467+
return lhs.value == rhs.value
468+
}
448469
}
449470

450471
/// A simple referential counter type that encodes as a single Int value.
451-
fileprivate final class Counter : Codable {
472+
fileprivate final class Counter : Codable, Equatable {
452473
var count: Int = 0
453474

454475
init() {}
@@ -462,6 +483,10 @@ fileprivate final class Counter : Codable {
462483
var container = encoder.singleValueContainer()
463484
try container.encode(self.count)
464485
}
486+
487+
static func ==(_ lhs: Counter, _ rhs: Counter) -> Bool {
488+
return lhs === rhs || lhs.count == rhs.count
489+
}
465490
}
466491

467492
// MARK: - Structured Types
@@ -612,6 +637,62 @@ fileprivate struct DoubleNaNPlaceholder : Codable, Equatable {
612637
}
613638
}
614639

640+
/// A type which encodes as an array directly through a single value container.
641+
struct Numbers : Codable, Equatable {
642+
let values = [4, 8, 15, 16, 23, 42]
643+
644+
init() {}
645+
646+
init(from decoder: Decoder) throws {
647+
let container = try decoder.singleValueContainer()
648+
let decodedValues = try container.decode([Int].self)
649+
guard decodedValues == values else {
650+
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "The Numbers are wrong!"))
651+
}
652+
}
653+
654+
func encode(to encoder: Encoder) throws {
655+
var container = encoder.singleValueContainer()
656+
try container.encode(values)
657+
}
658+
659+
static func ==(_ lhs: Numbers, _ rhs: Numbers) -> Bool {
660+
return lhs.values == rhs.values
661+
}
662+
663+
static var testValue: Numbers {
664+
return Numbers()
665+
}
666+
}
667+
668+
/// A type which encodes as a dictionary directly through a single value container.
669+
fileprivate final class Mapping : Codable, Equatable {
670+
let values: [String : URL]
671+
672+
init(values: [String : URL]) {
673+
self.values = values
674+
}
675+
676+
init(from decoder: Decoder) throws {
677+
let container = try decoder.singleValueContainer()
678+
values = try container.decode([String : URL].self)
679+
}
680+
681+
func encode(to encoder: Encoder) throws {
682+
var container = encoder.singleValueContainer()
683+
try container.encode(values)
684+
}
685+
686+
static func ==(_ lhs: Mapping, _ rhs: Mapping) -> Bool {
687+
return lhs === rhs || lhs.values == rhs.values
688+
}
689+
690+
static var testValue: Mapping {
691+
return Mapping(values: ["Apple": URL(string: "http://apple.com")!,
692+
"localhost": URL(string: "http://127.0.0.1")!])
693+
}
694+
}
695+
615696
struct NestedContainersTestType : Encodable {
616697
let testSuperEncoder: Bool
617698

@@ -713,6 +794,9 @@ JSONEncoderTests.test("testEncodingTopLevelSingleValueStruct") { TestJSONEncoder
713794
JSONEncoderTests.test("testEncodingTopLevelSingleValueClass") { TestJSONEncoder().testEncodingTopLevelSingleValueClass() }
714795
JSONEncoderTests.test("testEncodingTopLevelStructuredStruct") { TestJSONEncoder().testEncodingTopLevelStructuredStruct() }
715796
JSONEncoderTests.test("testEncodingTopLevelStructuredClass") { TestJSONEncoder().testEncodingTopLevelStructuredClass() }
797+
JSONEncoderTests.test("testEncodingTopLevelStructuredSingleStruct") { TestJSONEncoder().testEncodingTopLevelStructuredSingleStruct() }
798+
JSONEncoderTests.test("testEncodingTopLevelStructuredSingleClass") { TestJSONEncoder().testEncodingTopLevelStructuredSingleClass() }
799+
JSONEncoderTests.test("testEncodingTopLevelDeepStructuredType") { TestJSONEncoder().testEncodingTopLevelDeepStructuredType()}
716800
JSONEncoderTests.test("testEncodingTopLevelStructuredClass") { TestJSONEncoder().testEncodingTopLevelStructuredClass() }
717801
JSONEncoderTests.test("testEncodingTopLevelDeepStructuredType") { TestJSONEncoder().testEncodingTopLevelDeepStructuredType()}
718802
JSONEncoderTests.test("testEncodingOutputFormattingDefault") { TestJSONEncoder().testEncodingOutputFormattingDefault() }

0 commit comments

Comments
 (0)