Write a prettyPrinted JSON object with sorted keys in Swift

前端 未结 4 2162
故里飘歌
故里飘歌 2021-02-08 22:00

We often want to use JSON for human readability. As such, it is common to ask to sort the JSON keys alphabetically (or alphanumerically) in Go, in .NET, in Python, in Java, ...<

4条回答
  •  花落未央
    2021-02-08 22:46

    Pure Swift solution for iOS 7+, macOS 10.9+ (OS X Mavericks and up).

    A solution is to subclass NSDictionary (but not overriding the default init method as it wouldn't compile with 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)
            }
        }
    }
    

    With it, we can order the keys of our object with .forcedOrdering before writing it with .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)
    

    But be careful: you will need to subclass and sort all subdictionaries of your JSON object if you have any. Here is an extension for doing that recursion, inspired by Evgen Bodunov's gist (thank you).

    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) }
        }
    }
    

    Usage:

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

提交回复
热议问题