Skip to content

Commit 7a5af0f

Browse files
authored
Merge pull request #1197 from spevans/pr_json_serialise_more_types
JSONSerialization: Add missing numeric types
2 parents ace3990 + 5e435e7 commit 7a5af0f

File tree

3 files changed

+208
-149
lines changed

3 files changed

+208
-149
lines changed

Foundation/JSONSerialization.swift

Lines changed: 88 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,9 @@ open class JSONSerialization : NSObject {
6262
return true
6363
}
6464

65-
// 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 {
65+
if obj is String || obj is NSNull || obj is Int || obj is Bool || obj is UInt ||
66+
obj is Int8 || obj is Int16 || obj is Int32 || obj is Int64 ||
67+
obj is UInt8 || obj is UInt16 || obj is UInt32 || obj is UInt64 {
6768
return true
6869
}
6970

@@ -76,6 +77,10 @@ open class JSONSerialization : NSObject {
7677
return number.isFinite
7778
}
7879

80+
if let number = obj as? Decimal {
81+
return number.isFinite
82+
}
83+
7984
// object is Swift.Array
8085
if let array = obj as? [Any?] {
8186
for element in array {
@@ -99,8 +104,13 @@ open class JSONSerialization : NSObject {
99104
// object is NSNumber and is not NaN or infinity
100105
// For better performance, this (most expensive) test should be last.
101106
if let number = _SwiftValue.store(obj) as? NSNumber {
102-
let invalid = number.doubleValue.isInfinite || number.doubleValue.isNaN
103-
return !invalid
107+
if CFNumberIsFloatType(number._cfObject) {
108+
let dv = number.doubleValue
109+
let invalid = dv.isInfinite || dv.isNaN
110+
return !invalid
111+
} else {
112+
return true
113+
}
104114
}
105115

106116
// invalid object
@@ -285,9 +295,7 @@ internal extension JSONSerialization {
285295

286296
//MARK: - JSONSerializer
287297
private struct JSONWriter {
288-
289-
private let maxUIntLength = String(describing: UInt.max).count
290-
private let maxIntLength = String(describing: Int.max).count
298+
291299
var indent = 0
292300
let pretty: Bool
293301
let sortedKeys: Bool
@@ -320,13 +328,37 @@ private struct JSONWriter {
320328
case let boolValue as Bool:
321329
serializeBool(boolValue)
322330
case let num as Int:
323-
try serializeInt(value: num)
331+
serializeInteger(value: num)
332+
case let num as Int8:
333+
serializeInteger(value: num)
334+
case let num as Int16:
335+
serializeInteger(value: num)
336+
case let num as Int32:
337+
serializeInteger(value: num)
338+
case let num as Int64:
339+
serializeInteger(value: num)
324340
case let num as UInt:
325-
try serializeUInt(value: num)
341+
serializeInteger(value: num)
342+
case let num as UInt8:
343+
serializeInteger(value: num)
344+
case let num as UInt16:
345+
serializeInteger(value: num)
346+
case let num as UInt32:
347+
serializeInteger(value: num)
348+
case let num as UInt64:
349+
serializeInteger(value: num)
326350
case let array as Array<Any?>:
327351
try serializeArray(array)
328352
case let dict as Dictionary<AnyHashable, Any?>:
329353
try serializeDictionary(dict)
354+
case let num as Float:
355+
try serializeNumber(NSNumber(value: num))
356+
case let num as Double:
357+
try serializeNumber(NSNumber(value: num))
358+
case let num as Decimal:
359+
writer(num.description)
360+
case let num as NSDecimalNumber:
361+
writer(num.description)
330362
case is NSNull:
331363
try serializeNull()
332364
case _ where _SwiftValue.store(obj) is NSNumber:
@@ -336,92 +368,33 @@ private struct JSONWriter {
336368
}
337369
}
338370

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)
371+
private func serializeInteger<T: UnsignedInteger>(value: T, isNegative: Bool = false) {
372+
let maxIntLength = 22 // 20 digits in UInt64 + optional sign + trailing '\0'
373+
let asciiZero: CChar = 0x30 // ASCII '0' == 0x30
374+
let asciiMinus: CChar = 0x2d // ASCII '-' == 0x2d
375+
376+
var number = UInt64(value)
377+
var buffer = Array<CChar>(repeating: 0, count: maxIntLength)
378+
var pos = maxIntLength - 1
379+
380+
repeat {
381+
pos -= 1
382+
buffer[pos] = asciiZero + CChar(number % 10)
353383
number /= 10
384+
} while number != 0
385+
386+
if isNegative {
387+
pos -= 1
388+
buffer[pos] = asciiMinus
354389
}
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)
390+
let output = String(cString: Array(buffer.suffix(from: pos)))
391+
writer(output)
379392
}
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)
393+
394+
private func serializeInteger<T: SignedInteger>(value: T) {
395+
serializeInteger(value: UInt64(value.magnitude), isNegative: value < 0)
423396
}
424-
397+
425398
func serializeString(_ str: String) throws {
426399
writer("\"")
427400
for scalar in str.unicodeScalars {
@@ -462,15 +435,30 @@ private struct JSONWriter {
462435
}
463436

464437
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))
438+
if CFNumberIsFloatType(num._cfObject) {
439+
let dv = num.doubleValue
440+
if !dv.isFinite {
441+
let value: String
442+
if dv.isNaN {
443+
value = "NaN"
444+
} else if dv.isInfinite {
445+
value = "infinite"
446+
} else {
447+
value = String(dv)
448+
}
449+
450+
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: ["NSDebugDescription" : "Invalid number value (\(value)) in JSON write"])
451+
}
452+
453+
let string = CFNumberFormatterCreateStringWithNumber(nil, _numberformatter, num._cfObject)._swiftObject
454+
writer(string)
455+
} else {
456+
switch num._cfTypeID {
457+
case CFBooleanGetTypeID():
458+
serializeBool(num.boolValue)
459+
default:
460+
writer(num.stringValue)
461+
}
474462
}
475463
}
476464

@@ -578,13 +566,6 @@ private struct JSONWriter {
578566
}
579567
}
580568

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-
}
588569
}
589570

590571
//MARK: - JSONDeserializer

TestFoundation/TestJSONEncoder.swift

Lines changed: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -405,64 +405,30 @@ class TestJSONEncoder : XCTestCase {
405405
// UInt and Int
406406
func test_codingOfUIntMinMax() {
407407

408-
let encoder = JSONEncoder()
409-
410408
struct MyValue: Codable {
411-
let intMin:Int = Int.min
412-
let intMax:Int = Int.max
413-
let uintMin:UInt = UInt.min
414-
let uintMax:UInt = UInt.max
409+
let int64Min = Int64.min
410+
let int64Max = Int64.max
411+
let uint64Min = UInt64.min
412+
let uint64Max = UInt64.max
415413
}
416414

417-
let myValue = MyValue()
418-
let myDictI: [String:Any] = ["intMin": myValue.intMin, "intMax": myValue.intMax]
419-
let myDictU: [String:Any] = ["uintMin": myValue.uintMin, "uintMax": myValue.uintMax]
420-
let myDict1: [String:Any] = ["intMin": myValue.intMin]
421-
let myDict2: [String:Any] = ["intMax": myValue.intMax]
422-
let myDict3: [String:Any] = ["uintMin": myValue.uintMin]
423-
let myDict4: [String:Any] = ["uintMax": myValue.uintMax]
424-
425415
func compareJSON(_ s1: String, _ s2: String) {
426416
let ss1 = s1.trimmingCharacters(in: CharacterSet(charactersIn: "{}")).split(separator: Character(",")).sorted()
427417
let ss2 = s2.trimmingCharacters(in: CharacterSet(charactersIn: "{}")).split(separator: Character(",")).sorted()
428418
XCTAssertEqual(ss1, ss2)
429419
}
430420

431421
do {
422+
let encoder = JSONEncoder()
423+
let myValue = MyValue()
432424
let result = try encoder.encode(myValue)
433425
let r = String(data: result, encoding: .utf8) ?? "nil"
434-
compareJSON(r, "{\"uintMin\":0,\"uintMax\":18446744073709551615,\"intMin\":-9223372036854775808,\"intMax\":9223372036854775807}")
435-
436-
let resultI = try JSONSerialization.data(withJSONObject: myDictI)
437-
let rI = String(data: resultI, encoding: .utf8) ?? "nil"
438-
compareJSON(rI, "{\"intMin\":-9223372036854775808,\"intMax\":9223372036854775807}")
439-
440-
let resultU = try JSONSerialization.data(withJSONObject: myDictU)
441-
let rU = String(data: resultU, encoding: .utf8) ?? "nil"
442-
compareJSON(rU, "{\"uintMax\":18446744073709551615,\"uintMin\":0}")
443-
444-
let result1 = try JSONSerialization.data(withJSONObject: myDict1)
445-
let r1 = String(data: result1, encoding: .utf8) ?? "nil"
446-
XCTAssertEqual(r1, "{\"intMin\":-9223372036854775808}")
447-
448-
let result2 = try JSONSerialization.data(withJSONObject: myDict2)
449-
let r2 = String(data: result2, encoding: .utf8) ?? "nil"
450-
XCTAssertEqual(r2, "{\"intMax\":9223372036854775807}")
451-
452-
let result3 = try JSONSerialization.data(withJSONObject: myDict3)
453-
let r3 = String(data: result3, encoding: .utf8) ?? "nil"
454-
XCTAssertEqual(r3, "{\"uintMin\":0}")
455-
456-
let result4 = try JSONSerialization.data(withJSONObject: myDict4)
457-
let r4 = String(data: result4, encoding: .utf8) ?? "nil"
458-
XCTAssertEqual(r4, "{\"uintMax\":18446744073709551615}")
426+
compareJSON(r, "{\"uint64Min\":0,\"uint64Max\":18446744073709551615,\"int64Min\":-9223372036854775808,\"int64Max\":9223372036854775807}")
459427
} catch {
460428
XCTFail(String(describing: error))
461429
}
462430
}
463431

464-
465-
466432
// MARK: - Helper Functions
467433
private var _jsonEmptyDictionary: Data {
468434
return "{}".data(using: .utf8)!

0 commit comments

Comments
 (0)