基于Swift Decodable对象

时间:2017-09-11 13:42:01

标签: json swift swift4 decodable

鉴于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™那样做。有更聪明的方法吗?

2 个答案:

答案 0 :(得分:3)

我在这里做出一个假设 - 通过“Swift 4解码JSON的解码方式”,你的意思是调用try JSONDecoder().decode([Room].self, from: jsonData)。 如果是这种情况那么,据我所知,你运气不好,因为JSONDecoder将遍历其解析的JSON对象并在每个对象上调用初始值设定项Room(from: Decoder)。即使您要创建自己的初始化程序,也无法知道其他JSON对象包含的内容。

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

这是一个例子,可以作为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'方式:

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会根据出错的地方给你很好的具体错误! :)

答案 1 :(得分:2)

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

与Eivind一起去,因为他已经写了一个很好的答案,我只需加上我的2美分... JSON是一个对象模型,所以我解码JSON对象然后翻译那些对象是一流的Swift对象。只要服务器说JSON,你就必须假设它会在某个时刻发生变化,而你不想要的就是JSON对象模型在Swift世界中流入或指示对象结构甚至是变量名。因此,将对象解码为Plain Ol'是一个完美的选择。 Swift Objects(POSO)和类型,然后制作扩展,处理从这些POSO Decodable到您构建应用程序的对象的转换。我正在工作的游乐场就在下方,但是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)
    }
}