Skip to content

Commit 64b67c9

Browse files
committed
JSONSerialization: Add missing numeric types
- Add support for Int8/Int16/Int32/Int64, UInt8/UInt16/UInt32/UInt64, Decimal and NSDecimalNumber. - Improve serializeInteger performance to avoid overall speed regression.
1 parent c0ed4e8 commit 64b67c9

File tree

1 file changed

+76
-106
lines changed

1 file changed

+76
-106
lines changed

Foundation/JSONSerialization.swift

Lines changed: 76 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ open class JSONSerialization : NSObject {
6363
}
6464

6565
// object is Swift.String, NSNull, Int, Bool, or UInt
66-
if obj is String || obj is NSNull || obj is Int || obj is Bool || obj is UInt {
66+
if obj is String || obj is NSNull || obj is Int || obj is Bool || obj is UInt ||
67+
obj is Int8 || obj is Int16 || obj is Int32 || obj is Int64 ||
68+
obj is UInt8 || obj is UInt16 || obj is UInt32 || obj is UInt64 {
6769
return true
6870
}
6971

@@ -76,6 +78,10 @@ open class JSONSerialization : NSObject {
7678
return number.isFinite
7779
}
7880

81+
if let number = obj as? Decimal {
82+
return number.isFinite
83+
}
84+
7985
// object is Swift.Array
8086
if let array = obj as? [Any?] {
8187
for element in array {
@@ -99,8 +105,11 @@ open class JSONSerialization : NSObject {
99105
// object is NSNumber and is not NaN or infinity
100106
// For better performance, this (most expensive) test should be last.
101107
if let number = _SwiftValue.store(obj) as? NSNumber {
102-
let invalid = number.doubleValue.isInfinite || number.doubleValue.isNaN
103-
return !invalid
108+
if CFNumberIsFloatType(number._cfObject) {
109+
return number.doubleValue.isFinite
110+
} else {
111+
return true
112+
}
104113
}
105114

106115
// invalid object
@@ -285,9 +294,7 @@ internal extension JSONSerialization {
285294

286295
//MARK: - JSONSerializer
287296
private struct JSONWriter {
288-
289-
private let maxUIntLength = String(describing: UInt.max).count
290-
private let maxIntLength = String(describing: Int.max).count
297+
291298
var indent = 0
292299
let pretty: Bool
293300
let sortedKeys: Bool
@@ -320,13 +327,37 @@ private struct JSONWriter {
320327
case let boolValue as Bool:
321328
serializeBool(boolValue)
322329
case let num as Int:
323-
try serializeInt(value: num)
330+
serializeInteger(value: num)
331+
case let num as Int8:
332+
serializeInteger(value: num)
333+
case let num as Int16:
334+
serializeInteger(value: num)
335+
case let num as Int32:
336+
serializeInteger(value: num)
337+
case let num as Int64:
338+
serializeInteger(value: num)
324339
case let num as UInt:
325-
try serializeUInt(value: num)
340+
serializeInteger(value: num)
341+
case let num as UInt8:
342+
serializeInteger(value: num)
343+
case let num as UInt16:
344+
serializeInteger(value: num)
345+
case let num as UInt32:
346+
serializeInteger(value: num)
347+
case let num as UInt64:
348+
serializeInteger(value: num)
326349
case let array as Array<Any?>:
327350
try serializeArray(array)
328351
case let dict as Dictionary<AnyHashable, Any?>:
329352
try serializeDictionary(dict)
353+
case let num as Float:
354+
try serializeNumber(NSNumber(value: num))
355+
case let num as Double:
356+
try serializeNumber(NSNumber(value: num))
357+
case let num as Decimal:
358+
writer(num.description)
359+
case let num as NSDecimalNumber:
360+
writer(num.description)
330361
case is NSNull:
331362
try serializeNull()
332363
case _ where _SwiftValue.store(obj) is NSNumber:
@@ -336,92 +367,33 @@ private struct JSONWriter {
336367
}
337368
}
338369

339-
private func serializeUInt(value: UInt) throws {
340-
if value == 0 {
341-
writer("0")
342-
return
343-
}
344-
var array: [UInt] = []
345-
var stringResult = ""
346-
//Maximum length of an UInt
347-
array.reserveCapacity(maxUIntLength)
348-
stringResult.reserveCapacity(maxUIntLength)
349-
var number = value
350-
351-
while number != 0 {
352-
array.append(number % 10)
370+
private func serializeInteger<T: UnsignedInteger>(value: T, isNegative: Bool = false) {
371+
let maxIntLength = 22 // 20 digits in UInt64 + optional sign + trailing '\0'
372+
let ZERO: CChar = 0x30 // ASCII '0' == 0x30
373+
let MINUS: CChar = 0x2d // ASCII '-' == 0x2d
374+
375+
var number = UInt64(value)
376+
var buffer = Array<CChar>(repeating: 0, count: maxIntLength)
377+
var pos = maxIntLength - 1
378+
379+
repeat {
380+
pos -= 1
381+
buffer[pos] = ZERO + CChar(number % 10)
353382
number /= 10
383+
} while number != 0
384+
385+
if isNegative {
386+
pos -= 1
387+
buffer[pos] = MINUS
354388
}
355-
356-
/*
357-
Step backwards through the array and append the values to the string. This way the values are appended in the correct order.
358-
*/
359-
var counter = array.count
360-
while counter > 0 {
361-
counter -= 1
362-
let digit: UInt = array[counter]
363-
switch digit {
364-
case 0: stringResult.append("0")
365-
case 1: stringResult.append("1")
366-
case 2: stringResult.append("2")
367-
case 3: stringResult.append("3")
368-
case 4: stringResult.append("4")
369-
case 5: stringResult.append("5")
370-
case 6: stringResult.append("6")
371-
case 7: stringResult.append("7")
372-
case 8: stringResult.append("8")
373-
case 9: stringResult.append("9")
374-
default: fatalError()
375-
}
376-
}
377-
378-
writer(stringResult)
389+
let output = String(cString: Array(buffer.suffix(from: pos)))
390+
writer(output)
379391
}
380-
381-
private func serializeInt(value: Int) throws {
382-
if value == 0 {
383-
writer("0")
384-
return
385-
}
386-
var array: [Int] = []
387-
var stringResult = ""
388-
array.reserveCapacity(maxIntLength)
389-
//Account for a negative sign
390-
stringResult.reserveCapacity(maxIntLength + 1)
391-
var number = value
392-
393-
while number != 0 {
394-
array.append(number % 10)
395-
number /= 10
396-
}
397-
//If negative add minus sign before adding any values
398-
if value < 0 {
399-
stringResult.append("-")
400-
}
401-
/*
402-
Step backwards through the array and append the values to the string. This way the values are appended in the correct order.
403-
*/
404-
var counter = array.count
405-
while counter > 0 {
406-
counter -= 1
407-
let digit = array[counter]
408-
switch digit {
409-
case 0: stringResult.append("0")
410-
case 1, -1: stringResult.append("1")
411-
case 2, -2: stringResult.append("2")
412-
case 3, -3: stringResult.append("3")
413-
case 4, -4: stringResult.append("4")
414-
case 5, -5: stringResult.append("5")
415-
case 6, -6: stringResult.append("6")
416-
case 7, -7: stringResult.append("7")
417-
case 8, -8: stringResult.append("8")
418-
case 9, -9: stringResult.append("9")
419-
default: fatalError()
420-
}
421-
}
422-
writer(stringResult)
392+
393+
private func serializeInteger<T: SignedInteger>(value: T) {
394+
serializeInteger(value: UInt64(value.magnitude), isNegative: value < 0)
423395
}
424-
396+
425397
func serializeString(_ str: String) throws {
426398
writer("\"")
427399
for scalar in str.unicodeScalars {
@@ -462,15 +434,20 @@ private struct JSONWriter {
462434
}
463435

464436
mutating func serializeNumber(_ num: NSNumber) throws {
465-
if num.doubleValue.isInfinite || num.doubleValue.isNaN {
466-
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: ["NSDebugDescription" : "Number cannot be infinity or NaN"])
467-
}
468-
469-
switch num._cfTypeID {
470-
case CFBooleanGetTypeID():
471-
serializeBool(num.boolValue)
472-
default:
473-
writer(_serializationString(for: num))
437+
if CFNumberIsFloatType(num._cfObject) {
438+
if !num.doubleValue.isFinite {
439+
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: ["NSDebugDescription" : "Number cannot be infinity or NaN"])
440+
}
441+
442+
let string = CFNumberFormatterCreateStringWithNumber(nil, _numberformatter, num._cfObject)._swiftObject
443+
writer(string)
444+
} else {
445+
switch num._cfTypeID {
446+
case CFBooleanGetTypeID():
447+
serializeBool(num.boolValue)
448+
default:
449+
writer(num.stringValue)
450+
}
474451
}
475452
}
476453

@@ -578,13 +555,6 @@ private struct JSONWriter {
578555
}
579556
}
580557

581-
//[SR-2151] https://bugs.swift.org/browse/SR-2151
582-
private mutating func _serializationString(for number: NSNumber) -> String {
583-
if !CFNumberIsFloatType(number._cfObject) {
584-
return number.stringValue
585-
}
586-
return CFNumberFormatterCreateStringWithNumber(nil, _numberformatter, number._cfObject)._swiftObject
587-
}
588558
}
589559

590560
//MARK: - JSONDeserializer

0 commit comments

Comments
 (0)