diff --git a/Foundation/NSJSONSerialization.swift b/Foundation/NSJSONSerialization.swift index d9d5f09bf6..860194206d 100644 --- a/Foundation/NSJSONSerialization.swift +++ b/Foundation/NSJSONSerialization.swift @@ -7,6 +7,8 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // +import CoreFoundation + #if os(OSX) || os(iOS) import Darwin #elseif os(Linux) @@ -253,6 +255,14 @@ private struct JSONWriter { let pretty: 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, writer: @escaping (String?) -> Void) { self.pretty = pretty self.writer = writer @@ -310,7 +320,7 @@ private struct JSONWriter { writer("\"") } - func serializeNumber(_ num: NSNumber) throws { + mutating func serializeNumber(_ num: NSNumber) throws { if num.doubleValue.isInfinite || num.doubleValue.isNaN { throw NSError(domain: NSCocoaErrorDomain, code: NSCocoaError.PropertyListReadCorruptError.rawValue, userInfo: ["NSDebugDescription" : "Number cannot be infinity or NaN"]) } @@ -318,7 +328,7 @@ private struct JSONWriter { // Cannot detect type information (e.g. bool) as there is no objCType property on NSNumber in Swift // So, just print the number - writer("\(num)") + writer(_serializationString(for: num)) } mutating func serializeArray(_ array: [Any]) throws { @@ -402,6 +412,11 @@ private struct JSONWriter { writer(" ") } } + + //[SR-2151] https://bugs.swift.org/browse/SR-2151 + private mutating func _serializationString(for number: NSNumber) -> String { + return CFNumberFormatterCreateStringWithNumber(nil, _numberformatter, number._cfObject)._swiftObject + } } //MARK: - JSONDeserializer diff --git a/TestFoundation/TestNSJSONSerialization.swift b/TestFoundation/TestNSJSONSerialization.swift index 243b490dc1..de12fa6dc4 100644 --- a/TestFoundation/TestNSJSONSerialization.swift +++ b/TestFoundation/TestNSJSONSerialization.swift @@ -117,6 +117,8 @@ extension TestNSJSONSerialization { ("test_deserialize_badlyFormedArray", test_deserialize_badlyFormedArray), ("test_deserialize_invalidEscapeSequence", test_deserialize_invalidEscapeSequence), ("test_deserialize_unicodeMissingTrailingSurrogate", test_deserialize_unicodeMissingTrailingSurrogate), + ("test_serialize_dictionaryWithDecimal", test_serialize_dictionaryWithDecimal), + ] } @@ -624,6 +626,88 @@ extension TestNSJSONSerialization { XCTAssertEqual(try trySerialize(array2), "[]") } + //[SR-2151] https://bugs.swift.org/browse/SR-2151 + //NSJSONSerialization.data(withJSONObject:options) produces illegal JSON code + func test_serialize_dictionaryWithDecimal() { + + //test serialize values less than 1 with maxFractionDigits = 15 + func excecute_testSetLessThanOne() { + //expected : input to be serialized + let params = [ + ("0.1",0.1), + ("0.2",0.2), + ("0.3",0.3), + ("0.4",0.4), + ("0.5",0.5), + ("0.6",0.6), + ("0.7",0.7), + ("0.8",0.8), + ("0.9",0.9), + ("0.23456789012345",0.23456789012345), + + ("-0.1",-0.1), + ("-0.2",-0.2), + ("-0.3",-0.3), + ("-0.4",-0.4), + ("-0.5",-0.5), + ("-0.6",-0.6), + ("-0.7",-0.7), + ("-0.8",-0.8), + ("-0.9",-0.9), + ("-0.23456789012345",-0.23456789012345), + ] + for param in params { + let testDict = [param.0 : param.1] + let str = try? trySerialize(testDict) + XCTAssertEqual(str!, "{\"\(param.0)\":\(param.1)}", "serialized value should have a decimal places and leading zero") + } + } + //test serialize values grater than 1 with maxFractionDigits = 15 + func excecute_testSetGraterThanOne() { + let paramsBove1 = [ + ("1.1",1.1), + ("1.2",1.2), + ("1.23456789012345",1.23456789012345), + ("-1.1",-1.1), + ("-1.2",-1.2), + ("-1.23456789012345",-1.23456789012345), + ] + for param in paramsBove1 { + let testDict = [param.0 : param.1] + let str = try? trySerialize(testDict) + XCTAssertEqual(str!, "{\"\(param.0)\":\(param.1)}", "serialized Double should have a decimal places and leading value") + } + } + + //test serialize values for whole integer where the input is in Double format + func excecute_testWholeNumbersWithDoubleAsInput() { + + let paramsWholeNumbers = [ + ("-1" ,-1.0), + ("0" ,0.0), + ("1" ,1.0), + ] + for param in paramsWholeNumbers { + let testDict = [param.0 : param.1] + let str = try? trySerialize(testDict) + XCTAssertEqual(str!, "{\"\(param.0)\":\(NSString(string:param.0).intValue)}", "expect that serialized value should not contain trailing zero or decimal as they are whole numbers ") + } + } + + func excecute_testWholeNumbersWithIntInput() { + for i in -10..<10 { + let iStr = "\(i)" + let testDict = [iStr : i] + let str = try? trySerialize(testDict) + XCTAssertEqual(str!, "{\"\(iStr)\":\(i)}", "expect that serialized value should not contain trailing zero or decimal as they are whole numbers ") + } + } + excecute_testSetLessThanOne() + excecute_testSetGraterThanOne() + excecute_testWholeNumbersWithDoubleAsInput() + excecute_testWholeNumbersWithIntInput() + } + func test_serialize_null() { let arr = [NSNull()] XCTAssertEqual(try trySerialize(arr), "[null]")