Skip to content

Commit 4aa15bc

Browse files
DaveLiu888parkera
authored andcommitted
[SR-2151]NSJSONSerialization.data produces illegal JSON code #2 (#572)
* [SR-2151]NSJSONSerialization.data produces illegal JSON code NSJSONSerialization.data(withJSONObject:options) produces illegal JSON code https://bugs.swift.org/browse/SR-2151 * decoupling formatting logic and lazy loading formatter 1. moved format logic out of NSNumber and in to NSJSonSerialization 2. lazy load the formatter to be instantiated if needed 3. create a single format string to work with all formats since we are lazy loading a single formatter * fix build break update to latest master by removing casting and updating syntax
1 parent 585dc4c commit 4aa15bc

File tree

2 files changed

+101
-2
lines changed

2 files changed

+101
-2
lines changed

Foundation/NSJSONSerialization.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
88
//
99

10+
import CoreFoundation
11+
1012
#if os(OSX) || os(iOS)
1113
import Darwin
1214
#elseif os(Linux)
@@ -253,6 +255,14 @@ private struct JSONWriter {
253255
let pretty: Bool
254256
let writer: (String?) -> Void
255257

258+
private lazy var _numberformatter: CFNumberFormatter = {
259+
let formatter: CFNumberFormatter
260+
formatter = CFNumberFormatterCreate(nil, CFLocaleCopyCurrent(), kCFNumberFormatterNoStyle)
261+
CFNumberFormatterSetProperty(formatter, kCFNumberFormatterMaxFractionDigits, NSNumber(value: 15))
262+
CFNumberFormatterSetFormat(formatter, "0.###############"._cfObject)
263+
return formatter
264+
}()
265+
256266
init(pretty: Bool = false, writer: @escaping (String?) -> Void) {
257267
self.pretty = pretty
258268
self.writer = writer
@@ -310,15 +320,15 @@ private struct JSONWriter {
310320
writer("\"")
311321
}
312322

313-
func serializeNumber(_ num: NSNumber) throws {
323+
mutating func serializeNumber(_ num: NSNumber) throws {
314324
if num.doubleValue.isInfinite || num.doubleValue.isNaN {
315325
throw NSError(domain: NSCocoaErrorDomain, code: NSCocoaError.PropertyListReadCorruptError.rawValue, userInfo: ["NSDebugDescription" : "Number cannot be infinity or NaN"])
316326
}
317327

318328
// Cannot detect type information (e.g. bool) as there is no objCType property on NSNumber in Swift
319329
// So, just print the number
320330

321-
writer("\(num)")
331+
writer(_serializationString(for: num))
322332
}
323333

324334
mutating func serializeArray(_ array: [Any]) throws {
@@ -402,6 +412,11 @@ private struct JSONWriter {
402412
writer(" ")
403413
}
404414
}
415+
416+
//[SR-2151] https://bugs.swift.org/browse/SR-2151
417+
private mutating func _serializationString(for number: NSNumber) -> String {
418+
return CFNumberFormatterCreateStringWithNumber(nil, _numberformatter, number._cfObject)._swiftObject
419+
}
405420
}
406421

407422
//MARK: - JSONDeserializer

TestFoundation/TestNSJSONSerialization.swift

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ extension TestNSJSONSerialization {
117117
("test_deserialize_badlyFormedArray", test_deserialize_badlyFormedArray),
118118
("test_deserialize_invalidEscapeSequence", test_deserialize_invalidEscapeSequence),
119119
("test_deserialize_unicodeMissingTrailingSurrogate", test_deserialize_unicodeMissingTrailingSurrogate),
120+
("test_serialize_dictionaryWithDecimal", test_serialize_dictionaryWithDecimal),
121+
120122
]
121123
}
122124

@@ -624,6 +626,88 @@ extension TestNSJSONSerialization {
624626
XCTAssertEqual(try trySerialize(array2), "[]")
625627
}
626628

629+
//[SR-2151] https://bugs.swift.org/browse/SR-2151
630+
//NSJSONSerialization.data(withJSONObject:options) produces illegal JSON code
631+
func test_serialize_dictionaryWithDecimal() {
632+
633+
//test serialize values less than 1 with maxFractionDigits = 15
634+
func excecute_testSetLessThanOne() {
635+
//expected : input to be serialized
636+
let params = [
637+
("0.1",0.1),
638+
("0.2",0.2),
639+
("0.3",0.3),
640+
("0.4",0.4),
641+
("0.5",0.5),
642+
("0.6",0.6),
643+
("0.7",0.7),
644+
("0.8",0.8),
645+
("0.9",0.9),
646+
("0.23456789012345",0.23456789012345),
647+
648+
("-0.1",-0.1),
649+
("-0.2",-0.2),
650+
("-0.3",-0.3),
651+
("-0.4",-0.4),
652+
("-0.5",-0.5),
653+
("-0.6",-0.6),
654+
("-0.7",-0.7),
655+
("-0.8",-0.8),
656+
("-0.9",-0.9),
657+
("-0.23456789012345",-0.23456789012345),
658+
]
659+
for param in params {
660+
let testDict = [param.0 : param.1]
661+
let str = try? trySerialize(testDict)
662+
XCTAssertEqual(str!, "{\"\(param.0)\":\(param.1)}", "serialized value should have a decimal places and leading zero")
663+
}
664+
}
665+
//test serialize values grater than 1 with maxFractionDigits = 15
666+
func excecute_testSetGraterThanOne() {
667+
let paramsBove1 = [
668+
("1.1",1.1),
669+
("1.2",1.2),
670+
("1.23456789012345",1.23456789012345),
671+
("-1.1",-1.1),
672+
("-1.2",-1.2),
673+
("-1.23456789012345",-1.23456789012345),
674+
]
675+
for param in paramsBove1 {
676+
let testDict = [param.0 : param.1]
677+
let str = try? trySerialize(testDict)
678+
XCTAssertEqual(str!, "{\"\(param.0)\":\(param.1)}", "serialized Double should have a decimal places and leading value")
679+
}
680+
}
681+
682+
//test serialize values for whole integer where the input is in Double format
683+
func excecute_testWholeNumbersWithDoubleAsInput() {
684+
685+
let paramsWholeNumbers = [
686+
("-1" ,-1.0),
687+
("0" ,0.0),
688+
("1" ,1.0),
689+
]
690+
for param in paramsWholeNumbers {
691+
let testDict = [param.0 : param.1]
692+
let str = try? trySerialize(testDict)
693+
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 ")
694+
}
695+
}
696+
697+
func excecute_testWholeNumbersWithIntInput() {
698+
for i in -10..<10 {
699+
let iStr = "\(i)"
700+
let testDict = [iStr : i]
701+
let str = try? trySerialize(testDict)
702+
XCTAssertEqual(str!, "{\"\(iStr)\":\(i)}", "expect that serialized value should not contain trailing zero or decimal as they are whole numbers ")
703+
}
704+
}
705+
excecute_testSetLessThanOne()
706+
excecute_testSetGraterThanOne()
707+
excecute_testWholeNumbersWithDoubleAsInput()
708+
excecute_testWholeNumbersWithIntInput()
709+
}
710+
627711
func test_serialize_null() {
628712
let arr = [NSNull()]
629713
XCTAssertEqual(try trySerialize(arr), "[null]")

0 commit comments

Comments
 (0)