Skip to content

Commit 814d9fe

Browse files
committed
refactor: update performance for decoding array
1 parent 8a817dc commit 814d9fe

3 files changed

Lines changed: 188 additions & 3 deletions

File tree

Sources/ReerJSON/JSON.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ extension JSON {
144144
return yyjson_is_obj(pointer)
145145
}
146146

147+
@inline(__always)
148+
var isArray: Bool {
149+
return yyjson_is_arr(pointer)
150+
}
151+
147152
@inline(__always)
148153
func integer<T: FixedWidthInteger>() -> T? {
149154
guard let cString = yyjson_get_raw(pointer) else { return nil }

Sources/ReerJSON/JSONDecoderImpl.swift

Lines changed: 181 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,60 @@ final class JSONDecoderImpl: Decoder {
168168
if type == Decimal.self {
169169
return try unboxDecimal(from: value, for: codingPathNode, additionalKey) as! T
170170
}
171+
// Dictionary
171172
if !options.keyDecodingStrategy.isDefault, let dictType = type as? StringDecodableDictionary.Type {
172173
return try unboxDictionary(from: value, as: dictType, for: codingPathNode, additionalKey)
173174
}
175+
// Array (Written for performance.)
176+
if type == [String].self {
177+
return try unboxArray(from: value, as: [String].self, for: codingPathNode, additionalKey) as! T
178+
}
179+
if type == [Double].self {
180+
return try unboxArray(from: value, as: [Double].self, for: codingPathNode, additionalKey) as! T
181+
}
182+
if type == [Float].self {
183+
return try unboxArray(from: value, as: [Float].self, for: codingPathNode, additionalKey) as! T
184+
}
185+
if type == [Int].self {
186+
return try unboxArray(from: value, as: [Int].self, for: codingPathNode, additionalKey) as! T
187+
}
188+
if type == [Int8].self {
189+
return try unboxArray(from: value, as: [Int8].self, for: codingPathNode, additionalKey) as! T
190+
}
191+
if type == [Int16].self {
192+
return try unboxArray(from: value, as: [Int16].self, for: codingPathNode, additionalKey) as! T
193+
}
194+
if type == [Int32].self {
195+
return try unboxArray(from: value, as: [Int32].self, for: codingPathNode, additionalKey) as! T
196+
}
197+
if type == [Int64].self {
198+
return try unboxArray(from: value, as: [Int64].self, for: codingPathNode, additionalKey) as! T
199+
}
200+
if type == [UInt].self {
201+
return try unboxArray(from: value, as: [UInt].self, for: codingPathNode, additionalKey) as! T
202+
}
203+
if type == [UInt8].self {
204+
return try unboxArray(from: value, as: [UInt8].self, for: codingPathNode, additionalKey) as! T
205+
}
206+
if type == [UInt16].self {
207+
return try unboxArray(from: value, as: [UInt16].self, for: codingPathNode, additionalKey) as! T
208+
}
209+
if type == [UInt32].self {
210+
return try unboxArray(from: value, as: [UInt32].self, for: codingPathNode, additionalKey) as! T
211+
}
212+
if type == [UInt64].self {
213+
return try unboxArray(from: value, as: [UInt64].self, for: codingPathNode, additionalKey) as! T
214+
}
215+
#if compiler(>=6.0)
216+
if #available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *) {
217+
if type == [Int128].self {
218+
return try unboxArray(from: value, as: [Int128].self, for: codingPathNode, additionalKey) as! T
219+
}
220+
if type == [UInt128].self {
221+
return try unboxArray(from: value, as: [UInt128].self, for: codingPathNode, additionalKey) as! T
222+
}
223+
}
224+
#endif
174225

175226
return try with(value: value, path: codingPathNode.appending(additionalKey)) {
176227
try type.init(from: self)
@@ -336,6 +387,113 @@ final class JSONDecoderImpl: Decoder {
336387
return result as! T
337388
}
338389

390+
private func unboxArray<T: Decodable, K: CodingKey>(
391+
from value: JSON,
392+
as arrayType: [T].Type,
393+
for codingPathNode: CodingPathNode,
394+
_ additionalKey: K? = nil
395+
) throws -> [T] {
396+
try checkNotNull(value, expectedType: [T].self, for: codingPathNode, additionalKey)
397+
398+
guard value.isArray else {
399+
throw createTypeMismatchError(
400+
type: [T].self,
401+
for: codingPathNode.path(byAppending: additionalKey),
402+
value: value
403+
)
404+
}
405+
406+
let arraySize = yyjson_arr_size(value.pointer)
407+
let arrayCodingPathNode = codingPathNode.appending(additionalKey)
408+
409+
var result: [T] = []
410+
result.reserveCapacity(arraySize)
411+
412+
var iter = yyjson_arr_iter()
413+
guard yyjson_arr_iter_init(value.pointer, &iter) else {
414+
if arraySize == 0 {
415+
return result
416+
}
417+
throw DecodingError.dataCorrupted(.init(
418+
codingPath: codingPathNode.path(byAppending: additionalKey),
419+
debugDescription: "Failed to initialize array iterator."
420+
))
421+
}
422+
var index = 0
423+
424+
while let elementPtr = yyjson_arr_iter_next(&iter) {
425+
let elementValue = JSON(pointer: elementPtr)
426+
let indexKey = _CodingKey(index: index)
427+
428+
let decodedValue: T
429+
430+
if T.self == String.self {
431+
guard let string = options.json5 ? elementValue.stringWhenJSON5 : elementValue.string else {
432+
throw createTypeMismatchError(type: String.self, for: codingPathNode.path(byAppending: additionalKey), value: elementValue)
433+
}
434+
decodedValue = string as! T
435+
} else if T.self == Double.self {
436+
decodedValue = try unboxFloatingPoint(from: elementValue, as: Double.self, for: arrayCodingPathNode, indexKey) as! T
437+
} else if T.self == Float.self {
438+
decodedValue = try unboxFloatingPoint(from: elementValue, as: Float.self, for: arrayCodingPathNode, indexKey) as! T
439+
} else if T.self == Int.self {
440+
let intValue: Int = try unboxInteger(elementValue, for: arrayCodingPathNode, indexKey)
441+
decodedValue = intValue as! T
442+
} else if T.self == Int8.self {
443+
let intValue: Int8 = try unboxInteger(elementValue, for: arrayCodingPathNode, indexKey)
444+
decodedValue = intValue as! T
445+
} else if T.self == Int16.self {
446+
let intValue: Int16 = try unboxInteger(elementValue, for: arrayCodingPathNode, indexKey)
447+
decodedValue = intValue as! T
448+
} else if T.self == Int32.self {
449+
let intValue: Int32 = try unboxInteger(elementValue, for: arrayCodingPathNode, indexKey)
450+
decodedValue = intValue as! T
451+
} else if T.self == Int64.self {
452+
let intValue: Int64 = try unboxInteger(elementValue, for: arrayCodingPathNode, indexKey)
453+
decodedValue = intValue as! T
454+
} else if T.self == UInt.self {
455+
let intValue: UInt = try unboxInteger(elementValue, for: arrayCodingPathNode, indexKey)
456+
decodedValue = intValue as! T
457+
} else if T.self == UInt8.self {
458+
let intValue: UInt8 = try unboxInteger(elementValue, for: arrayCodingPathNode, indexKey)
459+
decodedValue = intValue as! T
460+
} else if T.self == UInt16.self {
461+
let intValue: UInt16 = try unboxInteger(elementValue, for: arrayCodingPathNode, indexKey)
462+
decodedValue = intValue as! T
463+
} else if T.self == UInt32.self {
464+
let intValue: UInt32 = try unboxInteger(elementValue, for: arrayCodingPathNode, indexKey)
465+
decodedValue = intValue as! T
466+
} else if T.self == UInt64.self {
467+
let intValue: UInt64 = try unboxInteger(elementValue, for: arrayCodingPathNode, indexKey)
468+
decodedValue = intValue as! T
469+
} else {
470+
#if compiler(>=6.0)
471+
if #available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *) {
472+
if T.self == Int128.self {
473+
let intValue: Int128 = try unboxInteger(elementValue, for: arrayCodingPathNode, indexKey)
474+
decodedValue = intValue as! T
475+
} else if T.self == UInt128.self {
476+
let intValue: UInt128 = try unboxInteger(elementValue, for: arrayCodingPathNode, indexKey)
477+
decodedValue = intValue as! T
478+
} else {
479+
decodedValue = try unbox(elementValue, as: T.self, for: arrayCodingPathNode, indexKey)
480+
}
481+
} else {
482+
decodedValue = try unbox(elementValue, as: T.self, for: arrayCodingPathNode, indexKey)
483+
}
484+
#else
485+
decodedValue = try unbox(elementValue, as: T.self, for: arrayCodingPathNode, indexKey)
486+
#endif
487+
}
488+
489+
result.append(decodedValue)
490+
index += 1
491+
}
492+
493+
return result
494+
}
495+
496+
339497
func unboxString<K: CodingKey>(from value: JSON, for codingPathNode: CodingPathNode, _ additionalKey: K? = nil) throws -> String {
340498
try checkNotNull(value, expectedType: String.self, for: codingPathNode, additionalKey)
341499

@@ -386,6 +544,28 @@ final class JSONDecoderImpl: Decoder {
386544

387545
throw self.createTypeMismatchError(type: F.self, for: codingPathNode.path(byAppending: additionalKey), value: value)
388546
}
547+
548+
@inline(__always)
549+
private func unboxInteger<T: FixedWidthInteger>(
550+
_ jsonValue: JSON,
551+
for codingPathNode: CodingPathNode,
552+
_ additionalKey: (some CodingKey)? = nil
553+
) throws -> T {
554+
guard let int: T = jsonValue.integer() else {
555+
guard jsonValue.isNumber else {
556+
throw createTypeMismatchError(
557+
type: T.self,
558+
for: codingPathNode.path(byAppending: additionalKey),
559+
value: jsonValue
560+
)
561+
}
562+
throw DecodingError.dataCorrupted(.init(
563+
codingPath: codingPath,
564+
debugDescription: "Number \(jsonValue.numberValue) is not representable in Swift."
565+
))
566+
}
567+
return int
568+
}
389569
}
390570

391571
// MARK: - SingleValueDecodingContainer
@@ -1459,7 +1639,7 @@ private struct JSONUnkeyedDecodingContainer: UnkeyedDecodingContainer {
14591639

14601640
@inline(__always)
14611641
private mutating func decodeInteger<T: FixedWidthInteger>(_ jsonValue: JSON) throws -> T {
1462-
guard let int: T = jsonValue.integer() else {
1642+
guard let int: T = jsonValue.integer() else {
14631643
guard jsonValue.isNumber else {
14641644
throw impl.createTypeMismatchError(type: T.self, for: currentCodingPath, value: jsonValue)
14651645
}

Sources/ReerJSON/Utilities.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ enum CodingPathNode: Sendable {
6868
}
6969
}
7070

71-
enum _CodingKey : CodingKey {
71+
enum _CodingKey: CodingKey {
7272
case string(String)
7373
case int(Int)
7474
case index(Int)
@@ -123,7 +123,7 @@ protocol StringDecodableDictionary {
123123
static var elementType: Decodable.Type { get }
124124
}
125125

126-
extension Dictionary : StringDecodableDictionary where Key == String, Value: Decodable {
126+
extension Dictionary: StringDecodableDictionary where Key == String, Value: Decodable {
127127
static var elementType: Decodable.Type { return Value.self }
128128
}
129129

0 commit comments

Comments
 (0)