首页 > 解决方案 > 如何在 Swift 中使用动态键(在根级别)解码此 JSON?

问题描述

我正在尝试创建一个小应用程序来使用 SwiftUI 控制我的色调灯,但我无法通过这个 JSON 进行解码/迭代。有很多关于如何做到这一点的线程,我已经尝试了几十个,使用 CodingKeys,创建自定义解码器等等,但我似乎无法理解。所以这是我从 Hue Bridge 得到的 JSON:

{
    "1": {
        "state": {
            "on": true,
            "bri": 254,
            "hue": 8417,
            "sat": 140,
            "effect": "none",
            "xy": [
                0.4573,
                0.41
            ],
            "ct": 366,
            "alert": "select",
            "colormode": "ct",
            "mode": "homeautomation",
            "reachable": false
        },
        "swupdate": {
            "state": "noupdates",
            "lastinstall": "2021-08-26T12:56:12"
        },
        ...
    },
    "2": {
        "state": {
            "on": false,
            "bri": 137,
            "hue": 36334,
            "sat": 203,
            "effect": "none",
            "xy": [
                0.2055,
                0.3748
            ],
            "ct": 500,
            "alert": "select",
            "colormode": "xy",
            "mode": "homeautomation",
            "reachable": true
        },
        "swupdate": {
            "state": "noupdates",
            "lastinstall": "2021-08-13T12:29:48"
        },
        ...
    },
    "9": {
        "state": {
            "on": false,
            "bri": 254,
            "hue": 16459,
            "sat": 216,
            "effect": "none",
            "xy": [
                0.4907,
                0.4673
            ],
            "alert": "none",
            "colormode": "xy",
            "mode": "homeautomation",
            "reachable": true
        },
        "swupdate": {
            "state": "noupdates",
            "lastinstall": "2021-08-12T12:51:18"
        },
        ...
    },
    ...
}

所以结构基本上是根级别的动态键和光信息,这是相同的。我创建了以下类型:

struct LightsObject: Decodable {
    public let lights: [String:LightInfo]
}
    
struct LightInfo: Decodable {
    var state: StateInfo
    struct StateInfo: Decodable {
        var on: Bool
        var bri: Int
        var hue: Int
        var sat: Int
        var effect: String
        var xy: [Double]
        var ct: Int
        var alert: String
        var colormode: String
        var reachable: Bool
    }
    
    var swupdate: UpdateInfo
    struct UpdateInfo: Decodable {
        var state: String
        var lastinstall: Date
    }
    ...

(它基本上继续包含对象中的所有变量)

我现在遇到的问题是我似乎无法将其放入普通数组或字典中。我会选择像 {"1":LightInfo, "2", LightInfo} 这样我可以迭代的东西,或者只是一个简单的 [LightInfo, LightInfo, ...],因为我什至可能不需要索引。

然后,理想情况下,我可以做类似的事情

ForEach(lights) { light in
   Text(light.name)
}

我尝试过创建自定义编码键,将类型实现为 Codable,等等,但我找不到适合我的解决方案。我知道,关于这个主题有很多线程,但我觉得我的初始设置可能是错误的,这就是它不起作用的原因。

这是解码部分:

            let task = urlSession.dataTask(with: apiGetLightsUrl, completionHandler: { (data, response, error) in
                guard let data = data, error == nil else { return }
                
                do {
                    let lights = try JSONDecoder().decode([String: LightInfo].self, from: data)
                    completionHandler(lights, nil)
                } catch {
                    print("couldn't get lights")
                    completionHandler(nil, error)
                }
            })

我实际上得到了 JSON,没问题,但正如我所说,我还没有能够解码它最新的错误是:

Optional(Swift.DecodingError.typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "1", intValue: nil), CodingKeys(stringValue: "swupdate", intValue: nil), CodingKeys(stringValue: "lastinstall", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil)))

我看过一些帖子,但他们使用的 JSON 通常有一个顶级对象,比如

{
   "foo":
      {
         "bar": "foobar"
         ...
      },
      {
         "bar": "foobar2"
         ...
      },
      {
         "bar": "foobar3"
         ...
      }
   ...
}

所以处理有点不同,因为我可以创建一个像

struct Object: Decodable {
    var foo: [LightsInfo]
}

这是不可能的:(

谁能指出我正确的方向?谢谢!

更新:

谢谢大家,解决方案确实有效。我的结构中有一些错误,现在已修复。(拼写错误的变量名、可选值等)。现在唯一缺少的是如何遍历字典。如果我使用

ForEach(lights)...

斯威夫特抱怨说,它只应该用于静态字典。我试着这样做

ForEach(lights, id: \.key) { key, value in
   Button(action: {print(value.name)}) {
      Text("foo")
   }
}

但我收到此错误:Generic struct 'ForEach' requires that '[String : LightInfo]' conform to 'RandomAccessCollection'

更新 2:

所以这似乎有效:

struct LightsView: View {
    @Binding var lights: [String:LightInfo]
    
    var body: some View {
        VStack {
            ForEach(Array(lights.keys.enumerated()), id: \.element) { key, value in
                LightView(lightInfo: self.lights["\(value)"]!, lightId: Int(value) ?? 0)
            }
        }
    }
}

我将尝试清理代码并对其进行一些优化。开放的建议;)

标签: jsonswiftmacosswiftuidecode

解决方案


你似乎快到了。这是一个[String:LightInfo]. 您只需要对其进行解码(而不是将其包装在 JSON 中不存在的 LightsObject 中)。如果您不关心数字,则可以取消这些值。它应该是这样的:

let lights = try JSONDecoder().decode([String: LightInfo].self, from: data).values

推荐阅读