Skip to content

Commit ef66189

Browse files
authored
Merge pull request #1102 from ianpartridge/json-sortedkeys
2 parents 1b05ee5 + d067ae2 commit ef66189

File tree

2 files changed

+49
-9
lines changed

2 files changed

+49
-9
lines changed

Foundation/NSJSONSerialization.swift

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ extension JSONSerialization {
3131
public init(rawValue: UInt) { self.rawValue = rawValue }
3232

3333
public static let prettyPrinted = WritingOptions(rawValue: 1 << 0)
34+
public static let sortedKeys = WritingOptions(rawValue: 1 << 1)
3435
}
3536
}
3637

@@ -116,6 +117,7 @@ open class JSONSerialization : NSObject {
116117

117118
var writer = JSONWriter(
118119
pretty: opt.contains(.prettyPrinted),
120+
sortedKeys: opt.contains(.sortedKeys),
119121
writer: { (str: String?) in
120122
if let str = str {
121123
jsonStr.append(str)
@@ -289,6 +291,7 @@ private struct JSONWriter {
289291
private let maxIntLength = String(describing: Int.max).characters.count
290292
var indent = 0
291293
let pretty: Bool
294+
let sortedKeys: Bool
292295
let writer: (String?) -> Void
293296

294297
private lazy var _numberformatter: CFNumberFormatter = {
@@ -299,8 +302,9 @@ private struct JSONWriter {
299302
return formatter
300303
}()
301304

302-
init(pretty: Bool = false, writer: @escaping (String?) -> Void) {
305+
init(pretty: Bool = false, sortedKeys: Bool = false, writer: @escaping (String?) -> Void) {
303306
self.pretty = pretty
307+
self.sortedKeys = sortedKeys
304308
self.writer = writer
305309
}
306310

@@ -499,10 +503,10 @@ private struct JSONWriter {
499503
writer("\n")
500504
incAndWriteIndent()
501505
}
502-
506+
503507
var first = true
504-
505-
for (key, value) in dict {
508+
509+
func serializeDictionaryElement(key: AnyHashable, value: Any) throws {
506510
if first {
507511
first = false
508512
} else if pretty {
@@ -511,15 +515,37 @@ private struct JSONWriter {
511515
} else {
512516
writer(",")
513517
}
514-
515-
if key is String {
516-
try serializeString(key as! String)
518+
519+
if let key = key as? String {
520+
try serializeString(key)
517521
} else {
518522
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: ["NSDebugDescription" : "NSDictionary key must be NSString"])
519523
}
520524
pretty ? writer(": ") : writer(":")
521525
try serializeJSON(value)
522526
}
527+
528+
if sortedKeys {
529+
let elems = try dict.sorted(by: { a, b in
530+
guard let a = a.key as? String,
531+
let b = b.key as? String else {
532+
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: ["NSDebugDescription" : "NSDictionary key must be NSString"])
533+
}
534+
let options: NSString.CompareOptions = [.numeric, .caseInsensitive, .forcedOrdering]
535+
let range: Range<String.Index> = a.startIndex..<a.endIndex
536+
let locale = NSLocale.systemLocale()
537+
538+
return a.compare(b, options: options, range: range, locale: locale) == .orderedAscending
539+
})
540+
for elem in elems {
541+
try serializeDictionaryElement(key: elem.key, value: elem.value)
542+
}
543+
} else {
544+
for (key, value) in dict {
545+
try serializeDictionaryElement(key: key, value: value)
546+
}
547+
}
548+
523549
if pretty {
524550
writer("\n")
525551
decAndWriteIndent()

TestFoundation/TestNSJSONSerialization.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -945,11 +945,12 @@ extension TestNSJSONSerialization {
945945
("test_booleanJSONObject", test_booleanJSONObject),
946946
("test_serialize_dictionaryWithDecimal", test_serialize_dictionaryWithDecimal),
947947
("test_serializeDecimalNumberJSONObject", test_serializeDecimalNumberJSONObject),
948+
("test_serializeSortedKeys", test_serializeSortedKeys),
948949
]
949950
}
950951

951-
func trySerialize(_ obj: Any) throws -> String {
952-
let data = try JSONSerialization.data(withJSONObject: obj, options: [])
952+
func trySerialize(_ obj: Any, options: JSONSerialization.WritingOptions = []) throws -> String {
953+
let data = try JSONSerialization.data(withJSONObject: obj, options: options)
953954
guard let string = String(data: data, encoding: .utf8) else {
954955
XCTFail("Unable to create string")
955956
return ""
@@ -1334,6 +1335,19 @@ extension TestNSJSONSerialization {
13341335
} catch {
13351336
XCTFail("Failed during serialization")
13361337
}
1338+
}
1339+
1340+
func test_serializeSortedKeys() {
1341+
var dict: [String: Any]
1342+
1343+
dict = ["z": 1, "y": 1, "x": 1, "w": 1, "v": 1, "u": 1, "t": 1, "s": 1, "r": 1, "q": 1, ]
1344+
XCTAssertEqual(try trySerialize(dict, options: .sortedKeys), "{\"q\":1,\"r\":1,\"s\":1,\"t\":1,\"u\":1,\"v\":1,\"w\":1,\"x\":1,\"y\":1,\"z\":1}")
1345+
1346+
dict = ["aaaa": 1, "aaa": 1, "aa": 1, "a": 1]
1347+
XCTAssertEqual(try trySerialize(dict, options: .sortedKeys), "{\"a\":1,\"aa\":1,\"aaa\":1,\"aaaa\":1}")
1348+
1349+
dict = ["c": ["c":1,"b":1,"a":1],"b":["c":1,"b":1,"a":1],"a":["c":1,"b":1,"a":1]]
1350+
XCTAssertEqual(try trySerialize(dict, options: .sortedKeys), "{\"a\":{\"a\":1,\"b\":1,\"c\":1},\"b\":{\"a\":1,\"b\":1,\"c\":1},\"c\":{\"a\":1,\"b\":1,\"c\":1}}")
13371351
}
13381352

13391353
fileprivate func createTestFile(_ path: String,_contents: Data) -> String? {

0 commit comments

Comments
 (0)