如何在Swift中使用动态键(在根级别)解码这个JSON?

inkz8wg9  于 2023-04-22  发布在  Swift
关注(0)|答案(4)|浏览(140)

我试图创建一个小应用程序来用SwiftUI控制我的Hue灯光,但我就是不能设法解码/迭代这个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
    }
    ...

(it基本上继续包含对象中的所有变量)
我现在的问题是,我似乎无法将其放入普通的数组或字典中。我会满足于像{“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)...

Swift抱怨说,它只应该用于静态字典。我试过这样做

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

但我得到了这个错误:Generic struct 'ForEach' requires that '[String : LightInfo]' conform to 'RandomAccessCollection'

更新二:

这似乎起作用了:

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

我将尝试清理代码并优化它一点。)

rpppsulh

rpppsulh1#

你看起来差不多了。这是一个[String:LightInfo]。你只需要解码它(而不是将其 Package 在JSON中不存在的LightsObject中)。如果你不关心数字,你可以提取值。它应该是这样的:

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

j8ag8udp2#

我自己也遇到了类似的问题,我想处理给定的泛型键。这是我解决它的方法,适应你的代码。

struct LightsObject: Decodable {
    public var lights: [String:LightInfo]
    
    private enum CodingKeys: String, CodingKey {
        case lights
    }
    
    // Decode the JSON manually
    required public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.lights = [String:LightInfo]()
        if let lightsSubContainer = try? container.nestedContainer(keyedBy: GenericCodingKeys.self, forKey: .lights) {
            for key in lightsSubContainer.allKeys {
                if let lightInfo = try? lightsSubContainer.decode(LightInfo.self, forKey: key) {
                    self.lights?[key.stringValue] = lightInfo
                }
            }
        }
    }
}

public class GenericCodingKeys: CodingKey {
    public var stringValue: String
    public var intValue: Int?
    
    required public init?(stringValue: String) {
        self.stringValue = stringValue
    }
    
    required public init?(intValue: Int) {
        self.intValue = intValue
        stringValue = "\(intValue)"
    }
}
wd2eg0qa

wd2eg0qa3#

如果您只对根字典的值感兴趣,请添加自定义初始化器

struct LightsObject: Decodable {
    let lights: [LightInfo]
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let dictionary = try container.decode([String:LightInfo].self)
        lights = Array(dictionary.values)
    }
}

并解码

let result = try JSONDecoder().decode(LightsObject.self, from: data)
completionHandler(result.lights, nil)

它返回LightInfo对象的数组,您可以声明

@Binding var lights: [LightInfo]

当然,完成处理程序类型必须是([LightInfo]?, Error?) -> Void

pw9qyyiw

pw9qyyiw4#

struct DynamicCodingKey: CodingKey {
var stringValue: String
var intValue: Int?

init?(stringValue: String) {
    self.stringValue = stringValue
    self.intValue = nil
}

init?(intValue: Int) {
    self.stringValue = "\(intValue)"
    self.intValue = intValue
}

init(stringValue: String, intValue: Int?) {
    self.stringValue = stringValue
    self.intValue = intValue
}
}

struct EmailData: Codable {
let email: String
let lastEnumeratedTime: String
let verifySelection: String
let enumerationTime: String
let enumerationFrequency: String

enum CodingKeys: String, CodingKey {
    case email
    case lastEnumeratedTime = "LastEnumeratedTime"
    case verifySelection = "VerifySelection"
    case enumerationTime = "EnumerationTime"
    case enumerationFrequency = "EnumerationFrequency"
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: DynamicCodingKey.self)
    email = container.allKeys.first!.stringValue
    let emailContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: DynamicCodingKey(stringValue: email)!)
    lastEnumeratedTime = try emailContainer.decode(String.self, forKey: .lastEnumeratedTime)
    verifySelection = try emailContainer.decode(String.self, forKey: .verifySelection)
    enumerationTime = try emailContainer.decode(String.self, forKey: .enumerationTime)
    enumerationFrequency = try emailContainer.decode(String.self, forKey: .enumerationFrequency)
 }
}

下面是这个Codable模型的一个示例用法:

let json = """
{
 "test@abcd.com": {
"LastEnumeratedTime": "20-Apr-2023 at 9:52:26 AM",
"VerifySelection": "1",
"EnumerationTime": "20-Apr-2023 at 11:19:33 AM",
"EnumerationFrequency": "Days"
 }
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
let emailData = try! decoder.decode(EmailData.self, from: json)

print(emailData.email) // Output: "mannam@yopmail.com"
print(emailData.lastEnumeratedTime) // Output: "20-Apr-2023 at 9:52:26 AM"
print(emailData.verifySelection) // Output: "1"
print(emailData.enumerationTime) // Output: "20-Apr-2023 at 11:19:33 AM"
print(emailData.enumerationFrequency) // Output: "Days"

相关问题