在 Swift 中编写一个带有排序键的 prettyPrinted JSON 对象
Write a prettyPrinted JSON object with sorted keys in Swift
我们经常希望使用 JSON 来提高可读性。因此,通常要求按字母顺序(或字母数字)in Go, in .NET, in Python, in Java、...
对 JSON 键进行排序
但是如何在 Swift 中使用 JSON 键按字母顺序输出 JSON?
PrettyPrinted 输出很简单:
JSONSerialization.writeJSONObject(jsonObject, to: outputStream, options: [.prettyPrinted], error: nil)
然而,为了便于阅读,键并未按字母顺序排序。它们可能按照 NSDictionary.keyEnumerator() 给出的顺序排列。但遗憾的是,我们无法在 Swift 中继承 Dictionary、NSDictionary 或 CFDictionary,因此我们无法覆盖键顺序的行为。
[编辑:实际上,我们可以将 NSDictionary 子类化,请参阅下面我的回答之一]
这是一种可行的解决方法,仅适用于 macOSSwift 脚本。 不适用于 iOS。
我们使用不同的编程语言解决 Swift Foundation 限制(例如 Python 2.7)。
import Cocoa
// sample jsonObject
let jsonObject = ["hello": "world", "foo": "bar"]
// writing JSON to file
let jsonPath = "myJson.json"
let outputJSON = OutputStream(toFileAtPath: jsonPath, append: false)!
outputJSON.open()
_ = JSONSerialization.writeJSONObject(jsonObject, to: outputJSON, options: [], error: nil)
outputJSON.close()
// sortedKeys equivalent using `sort_keys=True`
// prettyPrinted equivalent using `indent=2, separators=(',', ' : ')`
// unicode using `io.open` and `ensure_ascii=False`
func shell(_ args: String...) {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
task.launch()
task.waitUntilExit()
}
shell("python", "-c", ""
+ "import io\n"
+ "import json\n"
+ "jsonPath = 'myJson.json'\n"
+ "with io.open(jsonPath, mode='r', encoding='utf8') as json_file:\n"
+ " all_data = json.load(json_file)\n"
+ "with io.open(jsonPath, mode='w', encoding='utf8') as json_file:\n"
+ " json_file.write(unicode(json.dumps(all_data, ensure_ascii=False, sort_keys=True, indent=2, separators=(',', ' : '))))\n"
)
对于 iOS 11+ 和 macOS High Sierra (10.13+),新选项 .sortedKeys 轻松解决问题:
JSONSerialization.writeJSONObject(jsonObject, to: outputStream, options: [.sortedKeys, .prettyPrinted], error: nil)
谢谢 Hamish 的提示。
iOS 7+、macOS 10.9+(OS X Mavericks 及以上)的解决方案。
一个解决方案是在 Objective-C 中继承 NSDictionary,然后使用框架(对于应用程序)或静态库(对于命令行工具)的子类。
对于此演示,我将使用 nicklockwood/OrderedDictionary (700+ lines of code) instead of doing it from scratch, but there may be untested alternatives like quinntaylor/CHOrderedDictionary or rhodgkins/RDHOrderedDictionary。要将其集成为框架,请在 PodFile 中添加此依赖项:
pod 'OrderedDictionary', '~> 1.4'
然后我们将对对象的键进行排序:
import OrderedDictionary
let orderedJson = MutableOrderedDictionary()
jsonObject.sorted { [=11=].0.compare(.0, options: [.forcedOrdering, .caseInsensitive]) == .orderedAscending }
.forEach { orderedJson.setObject([=11=].value, forKey: [=11=].key) }
(注意:setObject(_,forKey:)
特定于 MutableOrderedDictionary)
最后我们可以把它写成 prettyPrinted:
_ = JSONSerialization.writeJSONObject(orderedJson, to: outputJSON, options: [.prettyPrinted], error: nil)
但要小心:您需要对 JSON 对象的 all 个子字典进行子类化和排序。
Swift iOS 7+、macOS 10.9+(OS X Mavericks 及以上)的纯 Swift 解决方案。
一个解决方案是 subclass NSDictionary(但不要覆盖默认的 init
方法,因为它不会用 Swift 编译)。
class MutableOrderedDictionary: NSDictionary {
let _values: NSMutableArray = []
let _keys: NSMutableOrderedSet = []
override var count: Int {
return _keys.count
}
override func keyEnumerator() -> NSEnumerator {
return _keys.objectEnumerator()
}
override func object(forKey aKey: Any) -> Any? {
let index = _keys.index(of: aKey)
if index != NSNotFound {
return _values[index]
}
return nil
}
func setObject(_ anObject: Any, forKey aKey: String) {
let index = _keys.index(of: aKey)
if index != NSNotFound {
_values[index] = anObject
} else {
_keys.add(aKey)
_values.add(anObject)
}
}
}
有了它,我们可以在用 .prettyPrinted
写入对象之前用 .forcedOrdering
对对象的键进行排序:
// force ordering
let orderedJson = MutableOrderedDictionary()
jsonObject.sorted { [=11=].0.compare(.0, options: [.forcedOrdering, .caseInsensitive]) == .orderedAscending }
.forEach { orderedJson.setObject([=11=].value, forKey: [=11=].key) }
// write pretty printed
_ = JSONSerialization.writeJSONObject(orderedJson, to: outputJSON, options: [.prettyPrinted], error: nil)
但要小心:您需要对 JSON 对象的 all 子词典进行子类化和排序(如果有的话)。这是执行该递归的扩展,灵感来自 Evgen Bodunov's gist(谢谢)。
extension MutableOrderedDictionary {
private static let defaultOrder: ((String, Any), (String, Any)) -> Bool = {
[=12=].0.compare(.0, options: [.forcedOrdering, .caseInsensitive]) == .orderedAscending
}
static func sorted(object: Any, by areInIncreasingOrder: ((key: String, value: Value), (key: String, value: Value)) -> Bool = defaultOrder) -> Any {
if let dict = object as? [String: Any] {
return MutableOrderedDictionary(dict, by: areInIncreasingOrder)
} else if let array = object as? [Any] {
return array.map { sorted(object: [=12=], by: areInIncreasingOrder) }
} else {
return object
}
}
convenience init(_ dict: [String: Any], by areInIncreasingOrder: ((key: String, value: Value), (key: String, value: Value)) -> Bool = defaultOrder) {
self.init()
dict.sorted(by: areInIncreasingOrder)
.forEach { setObject(MutableOrderedDictionary.sorted(object: [=12=].value, by: areInIncreasingOrder), forKey: [=12=].key) }
}
}
用法:
// force ordering
let orderedJson = MutableOrderedDictionary(jsonObject)
// write pretty printed
_ = JSONSerialization.writeJSONObject(orderedJson, to: outputJSON, options: [.prettyPrinted], error: nil)
我们经常希望使用 JSON 来提高可读性。因此,通常要求按字母顺序(或字母数字)in Go, in .NET, in Python, in Java、...
对 JSON 键进行排序但是如何在 Swift 中使用 JSON 键按字母顺序输出 JSON?
PrettyPrinted 输出很简单:
JSONSerialization.writeJSONObject(jsonObject, to: outputStream, options: [.prettyPrinted], error: nil)
然而,为了便于阅读,键并未按字母顺序排序。它们可能按照 NSDictionary.keyEnumerator() 给出的顺序排列。但遗憾的是,我们无法在 Swift 中继承 Dictionary、NSDictionary 或 CFDictionary,因此我们无法覆盖键顺序的行为。
[编辑:实际上,我们可以将 NSDictionary 子类化,请参阅下面我的回答之一]
这是一种可行的解决方法,仅适用于 macOSSwift 脚本。 不适用于 iOS。
我们使用不同的编程语言解决 Swift Foundation 限制(例如 Python 2.7)。
import Cocoa
// sample jsonObject
let jsonObject = ["hello": "world", "foo": "bar"]
// writing JSON to file
let jsonPath = "myJson.json"
let outputJSON = OutputStream(toFileAtPath: jsonPath, append: false)!
outputJSON.open()
_ = JSONSerialization.writeJSONObject(jsonObject, to: outputJSON, options: [], error: nil)
outputJSON.close()
// sortedKeys equivalent using `sort_keys=True`
// prettyPrinted equivalent using `indent=2, separators=(',', ' : ')`
// unicode using `io.open` and `ensure_ascii=False`
func shell(_ args: String...) {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
task.launch()
task.waitUntilExit()
}
shell("python", "-c", ""
+ "import io\n"
+ "import json\n"
+ "jsonPath = 'myJson.json'\n"
+ "with io.open(jsonPath, mode='r', encoding='utf8') as json_file:\n"
+ " all_data = json.load(json_file)\n"
+ "with io.open(jsonPath, mode='w', encoding='utf8') as json_file:\n"
+ " json_file.write(unicode(json.dumps(all_data, ensure_ascii=False, sort_keys=True, indent=2, separators=(',', ' : '))))\n"
)
对于 iOS 11+ 和 macOS High Sierra (10.13+),新选项 .sortedKeys 轻松解决问题:
JSONSerialization.writeJSONObject(jsonObject, to: outputStream, options: [.sortedKeys, .prettyPrinted], error: nil)
谢谢 Hamish 的提示。
iOS 7+、macOS 10.9+(OS X Mavericks 及以上)的解决方案。
一个解决方案是在 Objective-C 中继承 NSDictionary,然后使用框架(对于应用程序)或静态库(对于命令行工具)的子类。
对于此演示,我将使用 nicklockwood/OrderedDictionary (700+ lines of code) instead of doing it from scratch, but there may be untested alternatives like quinntaylor/CHOrderedDictionary or rhodgkins/RDHOrderedDictionary。要将其集成为框架,请在 PodFile 中添加此依赖项:
pod 'OrderedDictionary', '~> 1.4'
然后我们将对对象的键进行排序:
import OrderedDictionary
let orderedJson = MutableOrderedDictionary()
jsonObject.sorted { [=11=].0.compare(.0, options: [.forcedOrdering, .caseInsensitive]) == .orderedAscending }
.forEach { orderedJson.setObject([=11=].value, forKey: [=11=].key) }
(注意:setObject(_,forKey:)
特定于 MutableOrderedDictionary)
最后我们可以把它写成 prettyPrinted:
_ = JSONSerialization.writeJSONObject(orderedJson, to: outputJSON, options: [.prettyPrinted], error: nil)
但要小心:您需要对 JSON 对象的 all 个子字典进行子类化和排序。
Swift iOS 7+、macOS 10.9+(OS X Mavericks 及以上)的纯 Swift 解决方案。
一个解决方案是 subclass NSDictionary(但不要覆盖默认的 init
方法,因为它不会用 Swift 编译)。
class MutableOrderedDictionary: NSDictionary {
let _values: NSMutableArray = []
let _keys: NSMutableOrderedSet = []
override var count: Int {
return _keys.count
}
override func keyEnumerator() -> NSEnumerator {
return _keys.objectEnumerator()
}
override func object(forKey aKey: Any) -> Any? {
let index = _keys.index(of: aKey)
if index != NSNotFound {
return _values[index]
}
return nil
}
func setObject(_ anObject: Any, forKey aKey: String) {
let index = _keys.index(of: aKey)
if index != NSNotFound {
_values[index] = anObject
} else {
_keys.add(aKey)
_values.add(anObject)
}
}
}
有了它,我们可以在用 .prettyPrinted
写入对象之前用 .forcedOrdering
对对象的键进行排序:
// force ordering
let orderedJson = MutableOrderedDictionary()
jsonObject.sorted { [=11=].0.compare(.0, options: [.forcedOrdering, .caseInsensitive]) == .orderedAscending }
.forEach { orderedJson.setObject([=11=].value, forKey: [=11=].key) }
// write pretty printed
_ = JSONSerialization.writeJSONObject(orderedJson, to: outputJSON, options: [.prettyPrinted], error: nil)
但要小心:您需要对 JSON 对象的 all 子词典进行子类化和排序(如果有的话)。这是执行该递归的扩展,灵感来自 Evgen Bodunov's gist(谢谢)。
extension MutableOrderedDictionary {
private static let defaultOrder: ((String, Any), (String, Any)) -> Bool = {
[=12=].0.compare(.0, options: [.forcedOrdering, .caseInsensitive]) == .orderedAscending
}
static func sorted(object: Any, by areInIncreasingOrder: ((key: String, value: Value), (key: String, value: Value)) -> Bool = defaultOrder) -> Any {
if let dict = object as? [String: Any] {
return MutableOrderedDictionary(dict, by: areInIncreasingOrder)
} else if let array = object as? [Any] {
return array.map { sorted(object: [=12=], by: areInIncreasingOrder) }
} else {
return object
}
}
convenience init(_ dict: [String: Any], by areInIncreasingOrder: ((key: String, value: Value), (key: String, value: Value)) -> Bool = defaultOrder) {
self.init()
dict.sorted(by: areInIncreasingOrder)
.forEach { setObject(MutableOrderedDictionary.sorted(object: [=12=].value, by: areInIncreasingOrder), forKey: [=12=].key) }
}
}
用法:
// force ordering
let orderedJson = MutableOrderedDictionary(jsonObject)
// write pretty printed
_ = JSONSerialization.writeJSONObject(orderedJson, to: outputJSON, options: [.prettyPrinted], error: nil)