鉴于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™
那样做。有更聪明的方法吗?
答案 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对象的麻烦。
在相同数据的表示之间进行转换 不同系统之间的通信是繁琐的,尽管是必要的, 编写软件的任务。
因为这些结构 表示可以非常相似,创建一个可能很诱人 更高级别的抽象,以自动映射这些不同的 表示。例如,类型可以定义之间的映射 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)
}
}