diff --git a/Foundation/JSONSerialization.swift b/Foundation/JSONSerialization.swift index 66ff0dfa69..7858917a01 100644 --- a/Foundation/JSONSerialization.swift +++ b/Foundation/JSONSerialization.swift @@ -301,14 +301,6 @@ private struct JSONWriter { let pretty: Bool let sortedKeys: Bool let writer: (String?) -> Void - - private lazy var _numberformatter: CFNumberFormatter = { - let formatter: CFNumberFormatter - formatter = CFNumberFormatterCreate(nil, CFLocaleCopyCurrent(), kCFNumberFormatterNoStyle) - CFNumberFormatterSetProperty(formatter, kCFNumberFormatterMaxFractionDigits, NSNumber(value: 15)) - CFNumberFormatterSetFormat(formatter, "0.###############"._cfObject) - return formatter - }() init(pretty: Bool = false, sortedKeys: Bool = false, writer: @escaping (String?) -> Void) { self.pretty = pretty @@ -334,35 +326,35 @@ private struct JSONWriter { case let str as String: try serializeString(str) case let boolValue as Bool: - serializeBool(boolValue) + writer(boolValue.description) case let num as Int: - serializeInteger(value: num) + writer(num.description) case let num as Int8: - serializeInteger(value: num) + writer(num.description) case let num as Int16: - serializeInteger(value: num) + writer(num.description) case let num as Int32: - serializeInteger(value: num) + writer(num.description) case let num as Int64: - serializeInteger(value: num) + writer(num.description) case let num as UInt: - serializeInteger(value: num) + writer(num.description) case let num as UInt8: - serializeInteger(value: num) + writer(num.description) case let num as UInt16: - serializeInteger(value: num) + writer(num.description) case let num as UInt32: - serializeInteger(value: num) + writer(num.description) case let num as UInt64: - serializeInteger(value: num) + writer(num.description) case let array as Array: try serializeArray(array) case let dict as Dictionary: try serializeDictionary(dict) case let num as Float: - try serializeNumber(NSNumber(value: num)) + try serializeFloat(num) case let num as Double: - try serializeNumber(NSNumber(value: num)) + try serializeFloat(num) case let num as Decimal: writer(num.description) case let num as NSDecimalNumber: @@ -370,39 +362,13 @@ private struct JSONWriter { case is NSNull: try serializeNull() case _ where _SwiftValue.store(obj) is NSNumber: - try serializeNumber(_SwiftValue.store(obj) as! NSNumber) + let num = _SwiftValue.store(obj) as! NSNumber + writer(num.description) default: throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: ["NSDebugDescription" : "Invalid object cannot be serialized"]) } } - private func serializeInteger(value: T, isNegative: Bool = false) { - let maxIntLength = 22 // 20 digits in UInt64 + optional sign + trailing '\0' - let asciiZero: CChar = 0x30 // ASCII '0' == 0x30 - let asciiMinus: CChar = 0x2d // ASCII '-' == 0x2d - - var number = UInt64(value) - var buffer = Array(repeating: 0, count: maxIntLength) - var pos = maxIntLength - 1 - - repeat { - pos -= 1 - buffer[pos] = asciiZero + CChar(number % 10) - number /= 10 - } while number != 0 - - if isNegative { - pos -= 1 - buffer[pos] = asciiMinus - } - let output = String(cString: Array(buffer.suffix(from: pos))) - writer(output) - } - - private func serializeInteger(value: T) { - serializeInteger(value: UInt64(value.magnitude), isNegative: value < 0) - } - func serializeString(_ str: String) throws { writer("\"") for scalar in str.unicodeScalars { @@ -434,37 +400,24 @@ private struct JSONWriter { writer("\"") } - func serializeBool(_ bool: Bool) { - switch bool { - case true: - writer("true") - case false: - writer("false") + private func serializeFloat(_ num: T) throws { + guard num.isFinite else { + throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: ["NSDebugDescription" : "Invalid number value (\(num)) in JSON write"]) } + var str = num.description + if str.hasSuffix(".0") { + str.removeLast(2) + } + writer(str) } mutating func serializeNumber(_ num: NSNumber) throws { if CFNumberIsFloatType(num._cfObject) { - let dv = num.doubleValue - if !dv.isFinite { - let value: String - if dv.isNaN { - value = "NaN" - } else if dv.isInfinite { - value = "infinite" - } else { - value = String(dv) - } - - throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: ["NSDebugDescription" : "Invalid number value (\(value)) in JSON write"]) - } - - let string = CFNumberFormatterCreateStringWithNumber(nil, _numberformatter, num._cfObject)._swiftObject - writer(string) + try serializeFloat(num.doubleValue) } else { switch num._cfTypeID { case CFBooleanGetTypeID(): - serializeBool(num.boolValue) + writer(num.boolValue.description) default: writer(num.stringValue) } diff --git a/TestFoundation/TestJSONSerialization.swift b/TestFoundation/TestJSONSerialization.swift index df45081f36..69f96a32c4 100644 --- a/TestFoundation/TestJSONSerialization.swift +++ b/TestFoundation/TestJSONSerialization.swift @@ -1232,18 +1232,35 @@ extension TestJSONSerialization { } func test_serialize_Float() { - XCTAssertEqual(try trySerialize([-Float.leastNonzeroMagnitude, Float.leastNonzeroMagnitude]), "[-0,0]") - XCTAssertEqual(try trySerialize([-Float.greatestFiniteMagnitude]), "[-340282346638529000000000000000000000000]") - XCTAssertEqual(try trySerialize([Float.greatestFiniteMagnitude]), "[340282346638529000000000000000000000000]") - XCTAssertEqual(try trySerialize([Float(-1), Float.leastNonzeroMagnitude, Float(1)]), "[-1,0,1]") + XCTAssertEqual(try trySerialize([-Float.leastNonzeroMagnitude, Float.leastNonzeroMagnitude]), "[-1e-45,1e-45]") + XCTAssertEqual(try trySerialize([-Float.greatestFiniteMagnitude]), "[-3.4028235e+38]") + XCTAssertEqual(try trySerialize([Float.greatestFiniteMagnitude]), "[3.4028235e+38]") + XCTAssertEqual(try trySerialize([Float(-1), Float.leastNonzeroMagnitude, Float(1)]), "[-1,1e-45,1]") } func test_serialize_Double() { - XCTAssertEqual(try trySerialize([-Double.leastNonzeroMagnitude, Double.leastNonzeroMagnitude]), "[-0,0]") - XCTAssertEqual(try trySerialize([-Double.leastNormalMagnitude, Double.leastNormalMagnitude]), "[-0,0]") - XCTAssertEqual(try trySerialize([-Double.greatestFiniteMagnitude]), "[-179769313486232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]") - XCTAssertEqual(try trySerialize([Double.greatestFiniteMagnitude]), "[179769313486232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]") + XCTAssertEqual(try trySerialize([-Double.leastNonzeroMagnitude, Double.leastNonzeroMagnitude]), "[-5e-324,5e-324]") + XCTAssertEqual(try trySerialize([-Double.leastNormalMagnitude, Double.leastNormalMagnitude]), "[-2.2250738585072014e-308,2.2250738585072014e-308]") + XCTAssertEqual(try trySerialize([-Double.greatestFiniteMagnitude]), "[-1.7976931348623157e+308]") + XCTAssertEqual(try trySerialize([Double.greatestFiniteMagnitude]), "[1.7976931348623157e+308]") XCTAssertEqual(try trySerialize([Double(-1.0), Double(1.0)]), "[-1,1]") + + // Test round-tripping Double values + let value1 = 7.7087009966199993 + let value2 = 7.7087009966200002 + let dict1 = ["value": value1] + let dict2 = ["value": value2] + let jsonData1 = try! JSONSerialization.data(withJSONObject: dict1) + let jsonData2 = try! JSONSerialization.data(withJSONObject: dict2) + let jsonString1 = String(decoding: jsonData1, as: UTF8.self) + let jsonString2 = String(decoding: jsonData2, as: UTF8.self) + + XCTAssertEqual(jsonString1, "{\"value\":7.708700996619999}") + XCTAssertEqual(jsonString2, "{\"value\":7.70870099662}") + let decodedDict1 = try! JSONSerialization.jsonObject(with: jsonData1) as! [String : Double] + let decodedDict2 = try! JSONSerialization.jsonObject(with: jsonData2) as! [String : Double] + XCTAssertEqual(decodedDict1["value"], value1) + XCTAssertEqual(decodedDict2["value"], value2) } func test_serialize_Decimal() {