@@ -4,27 +4,19 @@ struct JSONUnkeyedDecodingContainer: UnkeyedDecodingContainer {
4
4
let codingPath : [ CodingKey ]
5
5
let array : [ JSONValue ]
6
6
7
- let count : Int ? // protocol requirement to be optional
8
- var isAtEnd : Bool
7
+ var count : Int ? { self . array . count }
8
+ var isAtEnd : Bool { self . currentIndex >= ( self . count ?? 0 ) }
9
9
var currentIndex = 0
10
10
11
11
init ( impl: JSONDecoderImpl , codingPath: [ CodingKey ] , array: [ JSONValue ] ) {
12
12
self . impl = impl
13
13
self . codingPath = codingPath
14
14
self . array = array
15
-
16
- self . isAtEnd = array. count == 0
17
- self . count = array. count
18
15
}
19
16
20
17
mutating func decodeNil( ) throws -> Bool {
21
- if self . array [ self . currentIndex] == . null {
22
- defer {
23
- currentIndex += 1
24
- if currentIndex == count {
25
- isAtEnd = true
26
- }
27
- }
18
+ if try self . getNextValue ( ofType: Never . self) == . null {
19
+ self . currentIndex += 1
28
20
return true
29
21
}
30
22
@@ -34,41 +26,31 @@ struct JSONUnkeyedDecodingContainer: UnkeyedDecodingContainer {
34
26
}
35
27
36
28
mutating func decode( _ type: Bool . Type ) throws -> Bool {
37
- defer {
38
- currentIndex += 1
39
- if currentIndex == count {
40
- isAtEnd = true
41
- }
42
- }
43
-
44
- guard case . bool( let bool) = self . array [ self . currentIndex] else {
45
- throw createTypeMismatchError ( type: type, value: self . array [ self . currentIndex] )
29
+ let value = try self . getNextValue ( ofType: Bool . self)
30
+ guard case . bool( let bool) = value else {
31
+ throw createTypeMismatchError ( type: type, value: value)
46
32
}
47
33
34
+ self . currentIndex += 1
48
35
return bool
49
36
}
50
37
51
38
mutating func decode( _ type: String . Type ) throws -> String {
52
- defer {
53
- currentIndex += 1
54
- if currentIndex == count {
55
- isAtEnd = true
56
- }
57
- }
58
-
59
- guard case . string( let string) = self . array [ self . currentIndex] else {
60
- throw createTypeMismatchError ( type: type, value: self . array [ self . currentIndex] )
39
+ let value = try self . getNextValue ( ofType: String . self)
40
+ guard case . string( let string) = value else {
41
+ throw createTypeMismatchError ( type: type, value: value)
61
42
}
62
43
44
+ self . currentIndex += 1
63
45
return string
64
46
}
65
47
66
48
mutating func decode( _: Double . Type ) throws -> Double {
67
- try decodeLosslessStringConvertible ( )
49
+ try decodeBinaryFloatingPoint ( )
68
50
}
69
51
70
52
mutating func decode( _: Float . Type ) throws -> Float {
71
- try decodeLosslessStringConvertible ( )
53
+ try decodeBinaryFloatingPoint ( )
72
54
}
73
55
74
56
mutating func decode( _: Int . Type ) throws -> Int {
@@ -112,20 +94,33 @@ struct JSONUnkeyedDecodingContainer: UnkeyedDecodingContainer {
112
94
}
113
95
114
96
mutating func decode< T> ( _: T . Type ) throws -> T where T: Decodable {
115
- let decoder = try decoderForNextElement ( )
116
- return try T ( from: decoder)
97
+ let decoder = try decoderForNextElement ( ofType: T . self)
98
+ let result = try T ( from: decoder)
99
+
100
+ // Because of the requirement that the index not be incremented unless
101
+ // decoding the desired result type succeeds, it can not be a tail call.
102
+ // Hopefully the compiler still optimizes well enough that the result
103
+ // doesn't get copied around.
104
+ self . currentIndex += 1
105
+ return result
117
106
}
118
107
119
108
mutating func nestedContainer< NestedKey> ( keyedBy type: NestedKey . Type ) throws
120
109
-> KeyedDecodingContainer < NestedKey > where NestedKey: CodingKey
121
110
{
122
- let decoder = try decoderForNextElement ( )
123
- return try decoder. container ( keyedBy: type)
111
+ let decoder = try decoderForNextElement ( ofType: KeyedDecodingContainer< NestedKey> . self , isNested: true )
112
+ let container = try decoder. container ( keyedBy: type)
113
+
114
+ self . currentIndex += 1
115
+ return container
124
116
}
125
117
126
118
mutating func nestedUnkeyedContainer( ) throws -> UnkeyedDecodingContainer {
127
- let decoder = try decoderForNextElement ( )
128
- return try decoder. unkeyedContainer ( )
119
+ let decoder = try decoderForNextElement ( ofType: UnkeyedDecodingContainer . self, isNested: true )
120
+ let container = try decoder. unkeyedContainer ( )
121
+
122
+ self . currentIndex += 1
123
+ return container
129
124
}
130
125
131
126
mutating func superDecoder( ) throws -> Decoder {
@@ -134,17 +129,9 @@ struct JSONUnkeyedDecodingContainer: UnkeyedDecodingContainer {
134
129
}
135
130
136
131
extension JSONUnkeyedDecodingContainer {
137
- private mutating func decoderForNextElement( ) throws -> JSONDecoderImpl {
138
- defer {
139
- currentIndex += 1
140
- if currentIndex == count {
141
- isAtEnd = true
142
- }
143
- }
144
-
145
- let value = self . array [ self . currentIndex]
146
- var newPath = self . codingPath
147
- newPath. append ( ArrayKey ( index: self . currentIndex) )
132
+ private mutating func decoderForNextElement< T> ( ofType: T . Type , isNested: Bool = false ) throws -> JSONDecoderImpl {
133
+ let value = try self . getNextValue ( ofType: T . self, isNested: isNested)
134
+ let newPath = self . codingPath + [ ArrayKey ( index: self . currentIndex) ]
148
135
149
136
return JSONDecoderImpl (
150
137
userInfo: self . impl. userInfo,
@@ -153,6 +140,34 @@ extension JSONUnkeyedDecodingContainer {
153
140
)
154
141
}
155
142
143
+ /// - Note: Instead of having the `isNested` parameter, it would have been quite nice to just check whether
144
+ /// `T` conforms to either `KeyedDecodingContainer` or `UnkeyedDecodingContainer`. Unfortunately, since
145
+ /// `KeyedDecodingContainer` takes a generic parameter (the `Key` type), we can't just ask if `T` is one, and
146
+ /// type-erasure workarounds are not appropriate to this use case due to, among other things, the inability to
147
+ /// conform most of the types that would matter. We also can't use `KeyedDecodingContainerProtocol` for the
148
+ /// purpose, as it isn't even an existential and conformance to it can't be checked at runtime at all.
149
+ ///
150
+ /// However, it's worth noting that the value of `isNested` is always a compile-time constant and the compiler
151
+ /// can quite neatly remove whichever branch of the `if` is not taken during optimization, making doing it this
152
+ /// way _much_ more performant (for what little it matters given that it's only checked in case of an error).
153
+ @inline ( __always)
154
+ private func getNextValue< T> ( ofType: T . Type , isNested: Bool = false ) throws -> JSONValue {
155
+ guard !self . isAtEnd else {
156
+ if isNested {
157
+ throw DecodingError . valueNotFound ( T . self,
158
+ . init( codingPath: self . codingPath,
159
+ debugDescription: " Cannot get nested keyed container -- unkeyed container is at end. " ,
160
+ underlyingError: nil ) )
161
+ } else {
162
+ throw DecodingError . valueNotFound ( T . self,
163
+ . init( codingPath: [ ArrayKey ( index: self . currentIndex) ] ,
164
+ debugDescription: " Unkeyed container is at end. " ,
165
+ underlyingError: nil ) )
166
+ }
167
+ }
168
+ return self . array [ self . currentIndex]
169
+ }
170
+
156
171
@inline ( __always) private func createTypeMismatchError( type: Any . Type , value: JSONValue ) -> DecodingError {
157
172
let codingPath = self . codingPath + [ ArrayKey ( index: self . currentIndex) ]
158
173
return DecodingError . typeMismatch ( type, . init(
@@ -161,42 +176,32 @@ extension JSONUnkeyedDecodingContainer {
161
176
}
162
177
163
178
@inline ( __always) private mutating func decodeFixedWidthInteger< T: FixedWidthInteger > ( ) throws -> T {
164
- defer {
165
- currentIndex += 1
166
- if currentIndex == count {
167
- isAtEnd = true
168
- }
169
- }
170
-
171
- guard case . number( let number) = self . array [ self . currentIndex] else {
172
- throw self . createTypeMismatchError ( type: T . self, value: self . array [ self . currentIndex] )
179
+ let value = try self . getNextValue ( ofType: T . self)
180
+ guard case . number( let number) = value else {
181
+ throw self . createTypeMismatchError ( type: T . self, value: value)
173
182
}
174
183
175
184
guard let integer = T ( number) else {
176
185
throw DecodingError . dataCorruptedError ( in: self ,
177
186
debugDescription: " Parsed JSON number < \( number) > does not fit in \( T . self) . " )
178
187
}
179
188
189
+ self . currentIndex += 1
180
190
return integer
181
191
}
182
192
183
- @inline ( __always) private mutating func decodeLosslessStringConvertible< T: LosslessStringConvertible > ( ) throws -> T {
184
- defer {
185
- currentIndex += 1
186
- if currentIndex == count {
187
- isAtEnd = true
188
- }
189
- }
190
-
191
- guard case . number( let number) = self . array [ self . currentIndex] else {
192
- throw self . createTypeMismatchError ( type: T . self, value: self . array [ self . currentIndex] )
193
+ @inline ( __always) private mutating func decodeBinaryFloatingPoint< T: LosslessStringConvertible > ( ) throws -> T {
194
+ let value = try self . getNextValue ( ofType: T . self)
195
+ guard case . number( let number) = value else {
196
+ throw self . createTypeMismatchError ( type: T . self, value: value)
193
197
}
194
198
195
199
guard let float = T ( number) else {
196
200
throw DecodingError . dataCorruptedError ( in: self ,
197
201
debugDescription: " Parsed JSON number < \( number) > does not fit in \( T . self) . " )
198
202
}
199
203
204
+ self . currentIndex += 1
200
205
return float
201
206
}
202
207
}
0 commit comments