首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将JSON解码为与JSON模型不同的目标结构

将JSON解码为与JSON模型不同的目标结构
EN

Stack Overflow用户
提问于 2021-07-15 22:08:24
回答 2查看 61关注 0票数 1

我有以下JSON:

代码语言:javascript
复制
{
  "header":{
    "namespace":"Device",
    "name":"Response",
    "messageID":"60FA815A-DC432316",
    "payloadVersion":"1"
  },
  "payload":{
    "device":{
      "firmware":"1.23W",
      "name":"Device 1",
      "uuid":"0ba64a0c-7a88b278-0001",
      "security":{
        "code":"aXdAPqd2OO9sZ6evLKjo2Q=="
      }
    },
    "system":{
      "uptime":5680126
    }
  }
}

我使用quicktype.io创建了Swift结构:

代码语言:javascript
复制
// MARK: - Welcome
struct Welcome: Codable {
    let header: Header
    let payload: Payload
}

// MARK: - Header
struct Header: Codable {
    let namespace, name, messageID, payloadVersion: String
}

// MARK: - Payload
struct Payload: Codable {
    let device: Device
    let system: System
}

// MARK: - Device
struct Device: Codable {
    let firmware, name, uuid: String
    let security: Security
}

// MARK: - Security
struct Security: Codable {
    let code: String
}

// MARK: - System
struct System: Codable {
    let uptime: Int
}

但是,我已经有了一个Device类型,这是一个更小的类型:

代码语言:javascript
复制
struct Device: Identifiable {
    let id: UUID
    let ip: String
    let name: String
    let firmware: String
    let uptime: Double
    // ...
}

如何才能很好地将原始JSON数据解码为Device结构?请注意,我的Device是平面的,并且具有嵌套在原始Device响应模型中更深的字段。我有一个自定义的Decodable实现吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-07-16 00:45:55

您可以创建中间CodingKeys,但这通常会变得相当乏味和不必要。相反,您可以创建一个通用的"string-key“,如下所示:

代码语言:javascript
复制
struct AnyStringKey: CodingKey, Hashable, ExpressibleByStringLiteral {
    var stringValue: String
    init(stringValue: String) { self.stringValue = stringValue }
    init<S: StringProtocol>(_ stringValue: S) { self.init(stringValue: String(stringValue)) }
    var intValue: Int?
    init?(intValue: Int) { return nil }
    init(stringLiteral value: String) { self.init(value) }
}

这样,您就可以通过解码嵌套容器在单个解码器init中轻松导航您的结构:

代码语言:javascript
复制
extension Device: Decodable {

    init(from decoder: Decoder) throws {
        let root = try decoder.container(keyedBy: AnyStringKey.self)
        let header = try root.nestedContainer(keyedBy: AnyStringKey.self, forKey: "header")

        self.name = try header.decode(String.self, forKey: "name")

        let payload = try root.nestedContainer(keyedBy: AnyStringKey.self, forKey: "payload")
        let device = try payload.nestedContainer(keyedBy: AnyStringKey.self, forKey: "device")

        self.id = try device.decode(UUID.self, forKey: "uuid")
        self.firmware = try device.decode(String.self, forKey: "firmware")

        let system = try payload.nestedContainer(keyedBy: AnyStringKey.self, forKey: "system")
        self.uptime = try system.decode(Double.self, forKey: "uptime")
    }
}

(我跳过了ip,因为它不在您的数据中,并且我假设您的UUID只是一个拼写错误,因为它无效。)

有了这个,你应该能够解码你需要的任何部分。

这是非常简单和标准的,但如果你有很多东西要解码,它可能会变得有点乏味。在这种情况下,您可以使用helper函数对其进行改进。

代码语言:javascript
复制
extension KeyedDecodingContainer {
    func decode<T>(_ type: T.Type, forPath path: String) throws -> T
    where T : Decodable, Key == AnyStringKey {
        let components = path.split(separator: ".")
        guard !components.isEmpty else {
            throw DecodingError.keyNotFound(AnyStringKey(path),
                                            .init(codingPath: codingPath,
                                                  debugDescription: "Could not find path \(path)",
                                                  underlyingError: nil))
        }

        if components.count == 1 {
            return try decode(type, forKey: AnyStringKey(components[0]))
        } else {
            let container = try nestedContainer(keyedBy: AnyStringKey.self, forKey: AnyStringKey(components[0]))
            return try container.decode(type, forPath: components.dropFirst().joined(separator: "."))
        }
    }
}

这样,您就可以通过点分路径语法访问值:

代码语言:javascript
复制
extension Device: Decodable {
    init(from decoder: Decoder) throws {
        let root = try decoder.container(keyedBy: AnyStringKey.self)
        self.name = try root.decode(String.self, forPath: "header.name")
        self.id = try root.decode(UUID.self, forPath: "payload.device.uuid")
        self.firmware = try root.decode(String.self, forPath: "payload.device.firmware")
        self.uptime = try root.decode(Double.self, forPath: "payload.system.uptime")
    }
}
票数 2
EN

Stack Overflow用户

发布于 2021-07-15 22:22:42

我看到了两种可能的快速解决方案:

解决方案1:

重命名可编码设备:

代码语言:javascript
复制
struct Device: Codable {
    ...
}

转到

代码语言:javascript
复制
struct DeviceFromAPI: Codable { 
    ...
}

然后替换

代码语言:javascript
复制
struct Payload: Codable {
    let device: Device
    ...
}

转到

代码语言:javascript
复制
struct Payload: Codable {
    let device: DeviceFromAPI
    ...
}

Solution2:

使用嵌套结构。

将所有内容都放在Welcome中(顺便说一句,这是默认的QuickType.io名称,重命名它可能很有趣)。

代码语言:javascript
复制
struct Welcome: Codable {
    let header: Header
    let payload: Payload

    // MARK: - Header
    struct Header: Codable {
    let namespace, name, messageID, payloadVersion: String
    }
    ...
}

即使需要将Device放在Payload中,也可以使用Go。

然后,当你想引用你的可编码设备时,你只需要使用Welcome.Payload.DeviceWelcome.Device (取决于它的嵌套方式),当它是你自己的设备时,你只需要使用Device

然后

然后,只需为Device创建一个自定义的init(),并将Codable Device作为参数。

代码语言:javascript
复制
extension Device {
    init(withCodableDevice codableDevice: DeviceFromAPI) {
        self.firmware = codableDevice.firmware
        ...
    }

}

或者使用解决方案2:

代码语言:javascript
复制
extension Device {
    init(withCodableDevice codableDevice: Welcome.Payload.Device) {
        self.firmware = codableDevice.firmware
        ...
    }

}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68395400

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档