Skip to content

Commit 32d39a7

Browse files
DaveLiu888parkera
authored andcommitted
[SR-2151]NSJSONSerialization.data produces illegal JSON code (#540)
* [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 by removing casting update to latest master
1 parent db3df7c commit 32d39a7

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, 15._bridgeToObject())
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
@@ -114,6 +114,8 @@ extension TestNSJSONSerialization {
114114
("test_deserialize_badlyFormedArray", test_deserialize_badlyFormedArray),
115115
("test_deserialize_invalidEscapeSequence", test_deserialize_invalidEscapeSequence),
116116
("test_deserialize_unicodeMissingTrailingSurrogate", test_deserialize_unicodeMissingTrailingSurrogate),
117+
("test_serialize_dictionaryWithDecimal", test_serialize_dictionaryWithDecimal),
118+
117119
]
118120
}
119121

@@ -621,6 +623,88 @@ extension TestNSJSONSerialization {
621623
XCTAssertEqual(try trySerialize(array2), "[]")
622624
}
623625

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

0 commit comments

Comments
 (0)