Swift 4、如何使用Codable解码JSON并在解码后的对象之间创建引用?
In Swift 4, how can you use Codable to decode JSON and create references between the decoded objects?
如何使用 Codable
解码 JSON 并交叉引用创建的对象(不是结构)?在此示例中,我希望 Painting
class 具有也在 JSON 中定义的 Color
对象的数组。 (我也希望能够将它们编码回 JSON。)
奖励: 在这种情况下,我希望 Painting.colors
成为非可选 let
属性 而不是 var
.我不希望它在创建后发生变化,也不希望它永远为零。 (我宁愿使用空数组的默认值而不是 nil。)
class Art: Codable {
var colors: [Color]?
var Paintings: [Painting]?
}
class Color: Codable {
var id: String?
var hex: String?
}
class Painting: Codable {
var name: String?
var colors: [Color]?
}
let json = """
{
"colors": [
{"id": "black","hex": "000000"
},
{"id": "red", "hex": "FF0000"},
{"id": "blue", "hex": "0000FF"},
{"id": "green", "hex": "00FF00"},
{"id": "yellow", "hex": "FFFB00"},
{"id": "orange", "hex": "FF9300"},
{"id": "purple", "hex": "FF00FF"}
],
"paintings": [
{
"name": "Starry Night",
"colorIds": ["blue", "black", "purple", "yellow"]
},
{
"name": "The Scream",
"colorIds": ["orange", "black", "blue"]
},
{
"name": "Nighthawks",
"colorIds": ["green", "orange", "blue", "yellow"]
}
]
}
"""
let data = json.data(using: .utf8)
let art = try JSONDecoder().decode(Art.self, from: data!)
我考虑过的一些方法:
Manually encoding/decoding json。似乎有很多额外的工作,但也许它给了我需要的控制权?
分解 JSON 解码步骤。将 JSON 反序列化为字典,首先提取并解码颜色,然后是绘画(它将可以访问上下文中的颜色)。这感觉就像与 Codable
作斗争,后者希望您使用 Data
而不是 Dictionary
.
一次解码所有内容
让 Painting
在运行时通过动态 属性 动态查找 Color
。但在我开始真正的工作之前,我宁愿建立和验证所有对象关系,然后永远不要改变。但也许这是最简单的?
不使用 Codable
其他一些不好的点子
我投票决定重新打开您的问题,因为虽然 JSON 和 Codable
无法方便地使用它,但可以完成。您将不得不手动解码 JSON,因此问题变成:最不痛苦的方法是什么?
我的经验法则: 不要与 JSON 作对。按原样将其导入 Swift 值,然后您可以对其进行各种操作。为此,让我们定义一个紧跟 JSON:
的 RawArt
结构
fileprivate struct RawArt: Decodable {
struct RawPainting: Codable {
var name: String
var colorIds: [String]
}
var colors: [Color] // the Color class matches the JSON so no need to define a new struct
var paintings: [RawPainting] // the Painting class does not so we need a substitute struct
}
现在将原始 JSON 对象转换为您的 class:
class Art: Codable {
var colors: [Color]
var paintings: [Painting]
required init(from decoder: Decoder) throws {
let rawArt = try RawArt(from: decoder)
self.colors = rawArt.colors
self.paintings = rawArt.paintings.map { rawPainting in
let name = rawPainting.name
let colors = rawPainting.colorIds.flatMap { colorId in
rawArt.colors.first(where: { [=11=].id == colorId })
}
return Painting(name: name, colors: colors)
}
}
}
class Color: Codable {
var id: String
var hex: String
init(id: String, hex: String) {
self.id = id
self.hex = hex
}
}
// It does not transform into the JSON you want so you may as well remove Codable conformance
class Painting: Codable {
var name: String
var colors: [Color]
init(name: String, colors: [Color]) {
self.name = name
self.colors = colors
}
}
测试它实际上引用了一个 Color
对象:
let data = json.data(using: .utf8)
let art = try JSONDecoder().decode(Art.self, from: data!)
art.colors[0].id = "new_black"
print(art.paintings[0].colors[1].id) // the second color in Starry Night: new_black
一切都不是可选的,只需不到 20 行代码即可从 JSON.
中取消归档对象
如何使用 Codable
解码 JSON 并交叉引用创建的对象(不是结构)?在此示例中,我希望 Painting
class 具有也在 JSON 中定义的 Color
对象的数组。 (我也希望能够将它们编码回 JSON。)
奖励: 在这种情况下,我希望 Painting.colors
成为非可选 let
属性 而不是 var
.我不希望它在创建后发生变化,也不希望它永远为零。 (我宁愿使用空数组的默认值而不是 nil。)
class Art: Codable {
var colors: [Color]?
var Paintings: [Painting]?
}
class Color: Codable {
var id: String?
var hex: String?
}
class Painting: Codable {
var name: String?
var colors: [Color]?
}
let json = """
{
"colors": [
{"id": "black","hex": "000000"
},
{"id": "red", "hex": "FF0000"},
{"id": "blue", "hex": "0000FF"},
{"id": "green", "hex": "00FF00"},
{"id": "yellow", "hex": "FFFB00"},
{"id": "orange", "hex": "FF9300"},
{"id": "purple", "hex": "FF00FF"}
],
"paintings": [
{
"name": "Starry Night",
"colorIds": ["blue", "black", "purple", "yellow"]
},
{
"name": "The Scream",
"colorIds": ["orange", "black", "blue"]
},
{
"name": "Nighthawks",
"colorIds": ["green", "orange", "blue", "yellow"]
}
]
}
"""
let data = json.data(using: .utf8)
let art = try JSONDecoder().decode(Art.self, from: data!)
我考虑过的一些方法:
Manually encoding/decoding json。似乎有很多额外的工作,但也许它给了我需要的控制权?
分解 JSON 解码步骤。将 JSON 反序列化为字典,首先提取并解码颜色,然后是绘画(它将可以访问上下文中的颜色)。这感觉就像与
Codable
作斗争,后者希望您使用Data
而不是Dictionary
. 一次解码所有内容
让
Painting
在运行时通过动态 属性 动态查找Color
。但在我开始真正的工作之前,我宁愿建立和验证所有对象关系,然后永远不要改变。但也许这是最简单的?不使用 Codable
其他一些不好的点子
我投票决定重新打开您的问题,因为虽然 JSON 和 Codable
无法方便地使用它,但可以完成。您将不得不手动解码 JSON,因此问题变成:最不痛苦的方法是什么?
我的经验法则: 不要与 JSON 作对。按原样将其导入 Swift 值,然后您可以对其进行各种操作。为此,让我们定义一个紧跟 JSON:
的RawArt
结构
fileprivate struct RawArt: Decodable {
struct RawPainting: Codable {
var name: String
var colorIds: [String]
}
var colors: [Color] // the Color class matches the JSON so no need to define a new struct
var paintings: [RawPainting] // the Painting class does not so we need a substitute struct
}
现在将原始 JSON 对象转换为您的 class:
class Art: Codable {
var colors: [Color]
var paintings: [Painting]
required init(from decoder: Decoder) throws {
let rawArt = try RawArt(from: decoder)
self.colors = rawArt.colors
self.paintings = rawArt.paintings.map { rawPainting in
let name = rawPainting.name
let colors = rawPainting.colorIds.flatMap { colorId in
rawArt.colors.first(where: { [=11=].id == colorId })
}
return Painting(name: name, colors: colors)
}
}
}
class Color: Codable {
var id: String
var hex: String
init(id: String, hex: String) {
self.id = id
self.hex = hex
}
}
// It does not transform into the JSON you want so you may as well remove Codable conformance
class Painting: Codable {
var name: String
var colors: [Color]
init(name: String, colors: [Color]) {
self.name = name
self.colors = colors
}
}
测试它实际上引用了一个 Color
对象:
let data = json.data(using: .utf8)
let art = try JSONDecoder().decode(Art.self, from: data!)
art.colors[0].id = "new_black"
print(art.paintings[0].colors[1].id) // the second color in Starry Night: new_black
一切都不是可选的,只需不到 20 行代码即可从 JSON.
中取消归档对象