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!)

我考虑过的一些方法:

我投票决定重新打开您的问题,因为虽然 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.

中取消归档对象