Мы часто хотим использовать JSON для удобства чтения. Поэтому обычно просят отсортировать ключи JSON по алфавиту (или буквенно-цифровому) в Go, в .NET, < a href = "https://stackoverflow.com/questions/2774361/json-output-sorting-in-python"> в Python , в Java, ...

Но как вывести JSON с ключами JSON, отсортированными по алфавиту в Swift?

Вывод PrettyPrinted прост:

JSONSerialization.writeJSONObject(jsonObject, to: outputStream, options: [.prettyPrinted], error: nil)

Все же ключи не отсортированы в алфавитном порядке для удобства чтения. Вероятно, они в порядке, указанном NSDictionary.keyEnumerator (). Но, к сожалению, мы не можем подклассы Dictionary, NSDictionary или CFDictionary в Swift, поэтому мы не можем переопределить поведение порядка ключей.

[edit: на самом деле, мы можем создать подкласс NSDictionary, см. один из моих ответов ниже]

8
Cœur 4 Сен 2017 в 10:43

4 ответа

Лучший ответ

Для iOS 11+ и macOS High Sierra (10.13+) появилась новая опция . sortedKeys решает проблему легко:

JSONSerialization.writeJSONObject(jsonObject, to: outputStream, options: [.sortedKeys, .prettyPrinted], error: nil)

Спасибо Хэмишу за подсказку.

12
Cœur 4 Сен 2017 в 15:30

Вот один из возможных обходных путей, который работает только для macOS сценариев Swift. Это не для 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"
)
0
Cœur 4 Янв 2018 в 07:24

Решение Pure Swift для iOS 7+, macOS 10.9+ (OS X Mavericks и выше).

Решением является подкласс 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)
        }
    }
}

С его помощью мы можем упорядочить ключи нашего объекта с помощью .forcedOrdering перед тем, как записать его с помощью .prettyPrinted:

// force ordering
let orderedJson = MutableOrderedDictionary()
jsonObject.sorted { $0.0.compare($1.0, options: [.forcedOrdering, .caseInsensitive]) == .orderedAscending }
          .forEach { orderedJson.setObject($0.value, forKey: $0.key) }

// write pretty printed
_ = JSONSerialization.writeJSONObject(orderedJson, to: outputJSON, options: [.prettyPrinted], error: nil)

Но будьте осторожны: вам нужно будет создать подклассы и отсортировать все подкаталоги вашего JSON-объекта, если он у вас есть. Вот расширение для выполнения этой рекурсии, вдохновленное Евгением Бодуновым's gist (спасибо).

extension MutableOrderedDictionary {
    private static let defaultOrder: ((String, Any), (String, Any)) -> Bool = {
        $0.0.compare($1.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: $0, 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: $0.value, by: areInIncreasingOrder), forKey: $0.key) }
    }
}

Использование:

// force ordering
let orderedJson = MutableOrderedDictionary(jsonObject)

// write pretty printed
_ = JSONSerialization.writeJSONObject(orderedJson, to: outputJSON, options: [.prettyPrinted], error: nil)
2
Cœur 7 Фев 2020 в 17:18

Решение для iOS 7+, macOS 10.9+ (OS X Mavericks и выше).

Решением является создание подкласса NSDictionary в Objective-C , а затем использование подкласса из каркаса (для приложения) или статической библиотеки (для инструмента командной строки).

Для этой демонстрации я буду использовать nicklockwood / OrderedDictionary (более 700 строк кода) ) вместо того, чтобы делать это с нуля, но могут быть непроверенные альтернативы, такие как quinntaylor / CHOrderedDictionary или rhodgkins / RDHOrderedDictionary. Чтобы интегрировать это как Framework, добавьте эту зависимость в ваш PodFile:

pod 'OrderedDictionary', '~> 1.4'

Затем мы закажем ключи нашего объекта:

import OrderedDictionary

let orderedJson = MutableOrderedDictionary()
jsonObject.sorted { $0.0.compare($1.0, options: [.forcedOrdering, .caseInsensitive]) == .orderedAscending }
          .forEach { orderedJson.setObject($0.value, forKey: $0.key) }

(примечание: setObject(_,forKey:) относится к MutableOrderedDictionary)
И, наконец, мы можем написать это довольноPrinted:

_ = JSONSerialization.writeJSONObject(orderedJson, to: outputJSON, options: [.prettyPrinted], error: nil)

Но будьте осторожны: вам нужно создать подкласс и отсортировать все поддиректории вашего JSON-объекта.

0
Cœur 3 Фев 2020 в 08:11