【问题标题】:Model relations based on a key in the attributes of a Swift Decodable object基于 Swift Decodable 对象属性中的键的模型关系
【发布时间】:2017-09-11 13:42:00
【问题描述】:

鉴于 JSON:

[{
        "name": "TV",
        "room": "Living Room"
    },
    {
        "name": "LightBulb 1",
        "room": "Living Room"
    }
]


struct Room: Decodable {
  let name: String
  let devices: [Device]
}
struct Device: Decodable {
  let name: String
}

如何使用 Swift 4 Decodable 解码 JSON 的方式正确序列化我的模型结构?我想为设备的room 属性中的每个唯一字符串创建房间,并将这些设备添加到该给定房间的设备列表中。

一种方法是简单地在没有房间关系的情况下映射它,然后在我获得整个设备列表后解析该关系,只需在迭代时运行并按需创建房间。但这感觉不像The Swift 4™ 这样做的方式。有没有更聪明的方法?

【问题讨论】:

    标签: json swift swift4 decodable


    【解决方案1】:

    我在这里做一个假设——“Swift 4 Decodable 解码 JSON 的方式”是指调用 try JSONDecoder().decode([Room].self, from: jsonData)。 如果是这种情况,那么据我所知,您很不走运,因为 JSONDecoder 将遍历其解析的 JSON 对象并在每个对象上调用初始化程序 Room(from: Decoder)。即使您要创建自己的初始化程序,它也无法知道其他 JSON 对象包含什么。

    解决此问题的一种方法是创建一个反映每个 JSON 对象属性的中间 Decodable 结构,然后通过这些结构的数组创建您的 Rooms。

    这是一个示例,可以作为 Xcode 游乐场使用:

    import UIKit
    
    struct Room {
        let name:    String
        var devices: [Device]
    
        fileprivate struct DeviceInRoom: Decodable {
            let name: String
            let room: String
        }
    
        static func rooms(from data: Data) -> [Room]? {
            return (try? JSONDecoder().decode([DeviceInRoom].self, from: data))?.rooms()
        }
    }
    struct Device {
        let name: String
    }
    
    fileprivate extension Array where Element == Room.DeviceInRoom {
        func rooms() -> [Room] {
            var rooms = [Room]()
            self.forEach { deviceInRoom in
                if let index = rooms.index(where: { $0.name == deviceInRoom.room }) {
                    rooms[index].devices.append(Device(name: deviceInRoom.name))
                } else {
                    rooms.append(Room(name: deviceInRoom.room, devices: [Device(name: deviceInRoom.name)]))
                }
            }
            return rooms
        }
    }
    
    let json = """
    [
      {
        "name": "TV",
        "room": "Living Room"
      },
      {
        "name": "LightBulb 1",
        "room": "Living Room"
      }
    ]
    """
    
    if let data  = json.data(using: .utf8),
       let rooms = Room.rooms(from: data) {
    
        print(rooms)
    }
    

    或者 - 也许是一种更 Swift4'y 的方式:

    import UIKit
    
    struct Room {
        let name:    String
        var devices: [Device]
    }
    
    struct Device {
        let name: String
    }
    
    struct RoomContainer: Decodable {
    
        let rooms: [Room]
    
        private enum CodingKeys: String, CodingKey {
            case name
            case room
        }
    
        init(from decoder: Decoder) throws {
            var rooms = [Room]()
            var objects = try decoder.unkeyedContainer()
            while objects.isAtEnd == false {
                let container  = try objects.nestedContainer(keyedBy: CodingKeys.self)
                let deviceName = try container.decode(String.self, forKey: .name)
                let roomName   = try container.decode(String.self, forKey: .room)
                if let index = rooms.index(where: { $0.name == roomName }) {
                    rooms[index].devices.append(Device(name: deviceName))
                } else {
                    rooms.append(Room(name: roomName, devices: [Device(name: deviceName)]))
                }
            }
            self.rooms = rooms
        }
    }
    
    let json = """
    [
      {
        "name": "TV",
        "room": "Living Room"
      },
      {
        "name": "LightBulb 1",
        "room": "Living Room"
      }
    ]
    """
    
    if let data  = json.data(using: .utf8),
       let rooms = (try? JSONDecoder().decode(RoomContainer.self, from: data))?.rooms {
    
        print(rooms)
    }
    

    注意 - 我在上面的代码中使用了几次try?。显然你应该正确处理错误 - JSONDecoder 会给你很好的,具体的错误取决于哪里出了问题! :)

    【讨论】:

      【解决方案2】:

      从一个对象模型映射到另一个对象模型

      与 Eivind 一起,因为他已经写了一个很好的答案,我只加我的 2 美分...... JSON 是一个对象模型,所以我会解码 JSON 对象,然后将这些对象转换为第一个- 类 Swift 对象。只要服务器使用 JSON,您就必须假设它会在某个时候发生变化,而您不想要的一件事是 JSON 对象模型渗入或指示对象结构,甚至是 Swift 世界中的变量名称。因此,将对象解码为 Plain Ol' Swift 对象 (POSO) 和类型,然后进行扩展以处理从这些 POSO 可解码对象到您将构建应用程序的对象的转换,这是一个完美的选择。下面是我工作的操场,但 Eivind 击败了我,在他的第二个示例中,麻烦地生成了最终确定的纯 Swift 对象。

      Apple 关于 JSON 处理的博客有这个很好的引用

      Working with JSON in Swift

      在相同数据的表示之间进行转换,以便 不同系统之间的通信虽然是必要的,但很乏味, 编写软件的任务。

      因为这些结构 表示可能非常相似,创建一个 更高层次的抽象来自动映射这些不同的 申述。例如,一个类型可以定义一个映射 snake_case JSON 键和 camelCase 属性名称,以便 使用 Swift 反射从 JSON 自动初始化模型 API,例如 Mirror。

      但是,我们发现这些类型的 抽象往往不会比传统的提供显着的好处 使用 Swift 语言特性,反而让它变得更加困难 调试问题或处理边缘情况。在上面的例子中, 初始化器不仅从 JSON 中提取和映射值,还 初始化复杂数据类型并执行特定于域的输入 验证。基于反射的方法必须做得很好 长度以完成所有这些任务。记住这一点 在评估您自己的应用程序的可用策略时。 费用 少量重复可能明显少于采摘 不正确的抽象

      import Foundation
      import UIKit
      
      struct RoomJSON
      {
          let name: String
          let room: String
      }
      
      struct Room
      {
          let name: String
          let devices: [Device]
      }
      
      struct Device
      {
          let name: String
      }
      
      extension RoomJSON: Decodable {
          enum RoomJSONKeys: String, CodingKey
          {
              case name = "name"
              case room = "room"
          }
          
          init(from decoder: Decoder) throws
          {
              let container = try decoder.container(keyedBy: RoomJSONKeys.self)
              let name: String = try container.decode(String.self, forKey: .name)
              let room: String = try container.decode(String.self, forKey: .room)
              
              self.init(name: name, room: room)
          }
      }
      
      let json = """
      [{
          "name": "TV",
          "room": "Living Room"
       },
       {
          "name": "LightBulb 1",
          "room": "Living Room"
       }]
      """.data(using: .utf8)!
      
      var rooms: [RoomJSON]?
      do {
          rooms = try JSONDecoder().decode([RoomJSON].self, from: json)
      } catch {
          print("\(error)")
      }
      
      if let rooms = rooms {
          for room in rooms {
              print(room)
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2019-03-19
        • 1970-01-01
        • 2011-07-06
        • 1970-01-01
        • 1970-01-01
        • 2020-01-12
        • 1970-01-01
        • 2011-10-22
        • 1970-01-01
        相关资源
        最近更新 更多