Skip to content

Commit 2aaa3eb

Browse files
authored
Fixes a JSONUnkeyedDecodingContainer bug (#41)
1 parent 9911f92 commit 2aaa3eb

File tree

2 files changed

+91
-77
lines changed

2 files changed

+91
-77
lines changed

Sources/PureSwiftJSONCoding/Decoding/JSONUnkeyedDecodingContainer.swift

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -112,29 +112,20 @@ struct JSONUnkeyedDecodingContainer: UnkeyedDecodingContainer {
112112
}
113113

114114
mutating func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
115-
defer {
116-
currentIndex += 1
117-
if currentIndex == count {
118-
isAtEnd = true
119-
}
120-
}
121-
122-
let json = array[currentIndex]
123-
var newPath = self.codingPath
124-
newPath.append(ArrayKey(index: currentIndex))
125-
let decoder = JSONDecoderImpl(userInfo: impl.userInfo, from: json, codingPath: newPath)
126-
115+
let decoder = try self.decoderForNextElement()
127116
return try T.init(from: decoder)
128117
}
129118

130119
mutating func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws
131120
-> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey
132121
{
133-
return try impl.container(keyedBy: type)
122+
let decoder = try self.decoderForNextElement()
123+
return try decoder.container(keyedBy: type)
134124
}
135125

136126
mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
137-
return try impl.unkeyedContainer()
127+
let decoder = try self.decoderForNextElement()
128+
return try decoder.unkeyedContainer()
138129
}
139130

140131
mutating func superDecoder() throws -> Decoder {
@@ -144,6 +135,24 @@ struct JSONUnkeyedDecodingContainer: UnkeyedDecodingContainer {
144135

145136
extension JSONUnkeyedDecodingContainer {
146137

138+
private mutating func decoderForNextElement() throws -> JSONDecoderImpl {
139+
defer {
140+
currentIndex += 1
141+
if currentIndex == count {
142+
isAtEnd = true
143+
}
144+
}
145+
146+
let value = array[currentIndex]
147+
var newPath = self.codingPath
148+
newPath.append(ArrayKey(index: currentIndex))
149+
150+
return JSONDecoderImpl(
151+
userInfo : impl.userInfo,
152+
from : value,
153+
codingPath: newPath)
154+
}
155+
147156
@inline(__always) private func createTypeMismatchError(type: Any.Type, value: JSONValue) -> DecodingError {
148157
let codingPath = self.codingPath + [ArrayKey(index: currentIndex)]
149158
return DecodingError.typeMismatch(type, .init(

Tests/JSONCodingTests/Decoding/JSONUnkeyedDecodingContainerTests.swift

Lines changed: 68 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -9,99 +9,91 @@ class JSONUnkeyedDecodingContainerTests: XCTestCase {
99
func testDecodeNull() {
1010
let impl = JSONDecoderImpl(userInfo: [:], from: .array([.null]), codingPath: [])
1111

12-
do {
13-
var container = try impl.unkeyedContainer()
14-
let result = try container.decodeNil()
15-
XCTAssertEqual(result, true)
16-
XCTAssertEqual(container.currentIndex, 1)
17-
}
18-
catch {
19-
XCTFail("Unexpected error: \(error)")
20-
}
12+
var container: UnkeyedDecodingContainer?
13+
var result: Bool?
14+
XCTAssertNoThrow(container = try impl.unkeyedContainer())
15+
XCTAssertNoThrow(result = try container?.decodeNil())
16+
XCTAssertEqual(result, true)
17+
XCTAssertEqual(container?.currentIndex, 1)
18+
XCTAssertEqual(container?.isAtEnd, true)
2119
}
2220

2321
func testDecodeNullFromArray() {
2422
let impl = JSONDecoderImpl(userInfo: [:], from: .array([.object([:])]), codingPath: [])
2523

26-
do {
27-
var container = try impl.unkeyedContainer()
28-
let result = try container.decodeNil()
29-
XCTAssertEqual(result, false)
30-
XCTAssertEqual(container.currentIndex, 0)
31-
}
32-
catch {
33-
XCTFail("Unexpected error: \(error)")
34-
}
24+
var container: UnkeyedDecodingContainer?
25+
var result: Bool?
26+
XCTAssertNoThrow(container = try impl.unkeyedContainer())
27+
XCTAssertNoThrow(result = try container?.decodeNil())
28+
XCTAssertEqual(result, false)
29+
XCTAssertEqual(container?.currentIndex, 0)
30+
XCTAssertEqual(container?.isAtEnd, false)
3531
}
3632

3733
// MARK: - String -
3834

3935
func testDecodeString() {
4036
let impl = JSONDecoderImpl(userInfo: [:], from: .array([.string("hello world")]), codingPath: [])
4137

42-
do {
43-
var container = try impl.unkeyedContainer()
44-
let result = try container.decode(String.self)
45-
XCTAssertEqual(result, "hello world")
46-
}
47-
catch {
48-
XCTFail("Unexpected error: \(error)")
49-
}
38+
var container: UnkeyedDecodingContainer?
39+
var result: String?
40+
XCTAssertNoThrow(container = try impl.unkeyedContainer())
41+
XCTAssertNoThrow(result = try container?.decode(String.self))
42+
XCTAssertEqual(result, "hello world")
43+
XCTAssertEqual(container?.currentIndex, 1)
44+
XCTAssertEqual(container?.isAtEnd, true)
5045
}
5146

5247
func testDecodeStringFromNumber() {
5348
let impl = JSONDecoderImpl(userInfo: [:], from: .array([.number("123")]), codingPath: [])
49+
let type = String.self
5450

55-
do {
56-
var container = try impl.unkeyedContainer()
57-
let result = try container.decode(String.self)
58-
XCTFail("Did not expect to get a result: \(result)")
59-
}
60-
catch Swift.DecodingError.typeMismatch(let type, let context) {
51+
var container: UnkeyedDecodingContainer?
52+
XCTAssertNoThrow(container = try impl.unkeyedContainer())
53+
XCTAssertThrowsError(_ = try container?.decode(type.self)) { (error) in
54+
guard case Swift.DecodingError.typeMismatch(let type, let context) = error else {
55+
return XCTFail("Unexpected error: \(error)")
56+
}
57+
6158
// expected
6259
XCTAssertTrue(type == String.self)
6360
XCTAssertEqual(context.codingPath.count, 1)
6461
XCTAssertEqual(context.codingPath.first as? ArrayKey, ArrayKey(index: 0))
6562
XCTAssertEqual(context.debugDescription, "Expected to decode String but found a number instead.")
6663
}
67-
catch {
68-
XCTFail("Unexpected error: \(error)")
69-
}
7064
}
7165

7266
// MARK: - Bool -
7367

7468
func testDecodeBool() {
7569
let impl = JSONDecoderImpl(userInfo: [:], from: .array([.bool(false)]), codingPath: [])
7670

77-
do {
78-
var container = try impl.unkeyedContainer()
79-
let result = try container.decode(Bool.self)
80-
XCTAssertEqual(result, false)
81-
}
82-
catch {
83-
XCTFail("Unexpected error: \(error)")
84-
}
71+
var container: UnkeyedDecodingContainer?
72+
var result: Bool?
73+
XCTAssertNoThrow(container = try impl.unkeyedContainer())
74+
XCTAssertNoThrow(result = try container?.decode(Bool.self))
75+
XCTAssertEqual(result, false)
76+
XCTAssertEqual(container?.currentIndex, 1)
77+
XCTAssertEqual(container?.isAtEnd, true)
8578
}
8679

8780
func testDecodeBoolFromNumber() {
8881
let impl = JSONDecoderImpl(userInfo: [:], from: .array([.string("hallo")]), codingPath: [])
89-
90-
do {
91-
var container = try impl.unkeyedContainer()
92-
let result = try container.decode(Bool.self)
93-
XCTFail("Did not expect to get a result: \(result)")
94-
}
95-
catch Swift.DecodingError.typeMismatch(let type, let context) {
82+
let type = Bool.self
83+
84+
var container: UnkeyedDecodingContainer?
85+
XCTAssertNoThrow(container = try impl.unkeyedContainer())
86+
XCTAssertThrowsError(_ = try container?.decode(type.self)) { (error) in
87+
guard case Swift.DecodingError.typeMismatch(let type, let context) = error else {
88+
return XCTFail("Unexpected error: \(error)")
89+
}
90+
9691
// expected
9792
XCTAssertTrue(type == Bool.self)
9893
XCTAssertEqual(context.codingPath.count, 1)
9994
XCTAssertEqual(context.codingPath.first as? ArrayKey, ArrayKey(index: 0))
10095
XCTAssertEqual(context.debugDescription, "Expected to decode Bool but found a string instead.")
10196
}
102-
catch {
103-
XCTFail("Unexpected error: \(error)")
104-
}
10597
}
10698

10799
// MARK: - Integer -
@@ -399,24 +391,37 @@ class JSONUnkeyedDecodingContainerTests: XCTestCase {
399391
func testGetFloatTypeMismatch() {
400392
let type = Float.self
401393
let impl = JSONDecoderImpl(userInfo: [:], from: .array([.object([:])]), codingPath: [])
394+
395+
var container: UnkeyedDecodingContainer?
396+
XCTAssertNoThrow(container = try impl.unkeyedContainer())
397+
XCTAssertThrowsError(_ = try container?.decode(type.self)) { (error) in
398+
guard case Swift.DecodingError.typeMismatch(let type, let context) = error else {
399+
return XCTFail("Unexpected error: \(error)")
400+
}
402401

403-
do {
404-
var container = try impl.unkeyedContainer()
405-
let result = try container.decode(type.self)
406-
XCTFail("Did not expect to get a result: \(result)")
407-
}
408-
catch Swift.DecodingError.typeMismatch(let type, let context) {
409402
// expected
410403
XCTAssertTrue(type == Float.self)
411404
XCTAssertEqual(context.codingPath.count, 1)
412405
XCTAssertEqual(context.codingPath.first as? ArrayKey, ArrayKey(index: 0))
413406
XCTAssertEqual(context.debugDescription, "Expected to decode Float but found a dictionary instead.")
414407
}
415-
catch {
416-
XCTFail("Unexpected error: \(error)")
417-
}
418408
}
419409

420-
410+
// MARK: - Containers -
411+
412+
func testGetKeyedContainer() {
413+
let impl = JSONDecoderImpl(userInfo: [:], from: .array([.object(["foo": .string("bar")])]), codingPath: [])
414+
415+
enum CodingKeys: String, CodingKey {
416+
case foo
417+
}
418+
419+
var unkeyedContainer: UnkeyedDecodingContainer?
420+
var keyedContainer: KeyedDecodingContainer<CodingKeys>?
421+
XCTAssertNoThrow(unkeyedContainer = try impl.unkeyedContainer())
422+
XCTAssertNoThrow(keyedContainer = try unkeyedContainer?.nestedContainer(keyedBy: CodingKeys.self))
423+
XCTAssertEqual(unkeyedContainer?.isAtEnd, true)
424+
XCTAssertEqual("bar", try keyedContainer?.decode(String.self, forKey: .foo))
425+
}
421426
}
422427

0 commit comments

Comments
 (0)