首页 > 解决方案 > 快速解析 JSON。解析json文件的最佳结构是什么

问题描述

我正在尝试解析一个如下所示的 json 文件:

{
"MyApp": {
    "pro1" : {
        "enable": true
    },
    "pro2" : {
        "enable": true
    }
},
"pro3" : {
    "pro4" : true,
    "pro5" : true,
    "pro6" : true,
    "pro7" : true,
    "pro8" : true,
    "pro10" : true
},
"pro11": {
    "pro12": false,
    "pro13": false,
    "pro14": false
},
"pro15": {
    "prob16": true
},
"prob16": {
    "prob17": {
        "prob18": true,
    }
},
"prob19": {
    "prob20": {
        "prob21": {
            "prob22": false
        }
    }
},
"prob23": true,
"prob24": true
}

我正在尝试以一种易于访问的方式对其进行解析。我首先将 json 文件解析为 type 的 json 对象[String:Any],然后尝试将这些对放入[String:[String:[String:Bool]]],但后来我意识到一个问题是我不知道那里可能有多少层。也许对中会有对中的对..

但是如果知道层数,比如最大层数是 4,我还把它作为地图吗?其他 3 张地图中的地图?有没有更好的数据结构可以放入?

标签: jsonswiftjsonparser

解决方案


(这是部分答案,我怀疑您会立即有更多问题,但在我知道您将如何使用此数据结构之前,我不想编写您需要的帮助程序。)

正如您所说,每个阶段要么是一个布尔值,要么是另一个将字符串映射到更多阶段的层。所以说在一个类型。当您使用or来描述某事物时,通常会告诉您它是一个枚举。

// Each level of Settings is either a value (bool) or more settings.
enum Settings {
    // Note that this is not order-preserving; it's possible to fix that if needed
    indirect case settings([String: Settings])
    case value(Bool)
}

您不知道密钥,因此您需要“任何密钥”,这可能应该在 stdlib 中,但它很容易编写。

// A CodingKey that handle any string
struct AnyStringKey: CodingKey {
    var stringValue: String
    init?(stringValue: String) { self.stringValue = stringValue }
    var intValue: Int?
    init?(intValue: Int) { return nil }
}

有了这些,解码只是递归地遍历树,并解码一个级别或一个值。

extension Settings: Decodable {
    init(from decoder: Decoder) throws {
        // Try to treat this as a settings dictionary
        if let container = try? decoder.container(keyedBy: AnyStringKey.self) {
            // Turn all the keys in to key/settings pairs (recursively).
            let keyValues = try container.allKeys.map { key in
                (key.stringValue, try container.decode(Settings.self, forKey: key))
            }
            // Turn those into a dictionary (if dupes, keep the first)
            let level = Dictionary(keyValues, uniquingKeysWith: { first, _ in first })
            self = .settings(level)
        } else {
            // Otherwise, it had better be a boolen
            self = .value(try decoder.singleValueContainer().decode(Bool.self))
        }
    }
}

let result = try JSONDecoder().decode(Settings.self, from: json)

(您如何方便地访问它取决于您希望该表视图看起来像什么;每一行是什么,您的 UITableViewDataSource 是什么样的?如果您能在问题中解释您想要的方式,我很乐意提供帮助使用此数据。)

迅捷跑者


以下代码可能对您来说太复杂而无法真正使用,但我想探索一下您正在寻找什么样的界面。这个数据结构相当复杂,我还不清楚你想如何使用它。编写一些使用此结果的代码会有所帮助,然后我可以帮助编写与该调用代码匹配的代码。

但是你可以考虑这个数据结构的一种方式是它是一个“字典”,可以被一个“路径”索引,它是一个[String]. 所以一条路径是["prob23"],一条路径是["prob19", "prob20", "prob21", "prob22"]

所以要下标,我们可以这样做:

extension Settings {
    // This is generic so it can handle both [String] and Slice<[String]>
    // Some of this could be simplified by making a SettingsPath type.
    subscript<Path>(path: Path) -> Bool?
        where Path: Collection, Path.Element == String {
            switch self {
            case .value(let value):
                // If this is a value, and there's no more path, return the value
                return path.isEmpty ? value : nil

            case .settings(let settings):
                // If this is another layer of settings, recurse down one layer
                guard let key = path.first else { return nil }
                return settings[key]?[path.dropFirst()]

            }
    }
}

这不是一本真正的字典。它甚至不是一个真正的收藏。它只是一个带有下标语法的数据结构。但是有了这个,你可以说:

result[["pro3", "pro4"]] // true

而且,同样,你得到了所有的路径。

extension Settings {
    var paths: [[String]] {
        switch self {
        case .settings(let settings):

            // For each key, prepend it to all its children's keys until you get to a value
            let result: [[[String]]] = settings.map { kv in
                let key = kv.key
                let value = kv.value
                switch value {
                case .value:
                    return [[key]] // Base case
                case .settings:
                    return value.paths.map { [key] + $0 } // Recurse and add our key
                }
            }

            // The result of that is [[[String]]] because we looped twice over something
            // that was already an array. We want to flatten it back down one layer to [[String]]
            return Array(result.joined())
        case .value:
            return [] // This isn't the base case; this is just in case you call .paths on a value.
        }
    }
}

for path in result.paths {
    print("\(path): \(result[path]!)")
}

==>

["pro15", "prob16"]: true
["pro3", "pro4"]: true
["pro3", "pro10"]: true
["pro3", "pro7"]: true
["pro3", "pro8"]: true
["pro3", "pro5"]: true
["pro3", "pro6"]: true
["prob19", "prob20", "prob21", "prob22"]: false
["prob23"]: true
["prob24"]: true
["MyApp", "pro1", "enable"]: true
["MyApp", "pro2", "enable"]: true
["prob16", "prob17", "prob18"]: true
["pro11", "pro13"]: false
["pro11", "pro14"]: false
["pro11", "pro12"]: false

我知道这是一个过于复杂的答案,但它可能会让您开始以正确的方式思考问题以及您希望从这个数据结构中得到什么。找出您的用例,其余的将由此产生。


推荐阅读