在Swift 4中,如何使用Codable解码JSON并在解码对象之间创建引用?

问题描述:

如何使用Codable解码JSON并在创建时交叉引用对象(不是结构体)?在这个例子中,我希望Painting类具有也在JSON中定义的Color对象的数组。 (我也希望能够将它们编码回JSON了。)在Swift 4中,如何使用Codable解码JSON并在解码对象之间创建引用?

奖励:在这种情况下,我宁愿Painting.colors是一个非可选let属性而不是var。我不希望它在创建后改变,我不希望它永远是零。 (我宁愿使用一个空数组,而不是零的默认值。)

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。但在我开始真正的工作之前,我宁愿让所有的对象关系建立和验证,然后永远不要改变。但也许这将是最简单的?

  • 不使用可编码

  • 其他一些不好的想法

+0

我会完全反对JSON。它没有(本地)表达引用的能力。 – Alexander

+0

简短的回答是:JSON不能做到这一点。如果你想要一个存档器(即它比序列化为JSON的功能更强大),目前您需要构建自己的,或者找到构建它的其他人。它在stdlib中不存在。 'NSKeyedArchiver'是当前可用的Cocoa工具。 –

我投票重新开启自己的问题,因为虽然它并不方便可行的使用JSON和Codable,是可以做到的。你将不得不手动解码JSON,这样问题就变成了:做这件事最痛苦的方法是什么?

我的经验法则:不打JSON。将它原样导入Swift值,然后您可以对其执行各种操作。为此,让我们定义一个RawArt结构是紧跟JSON:

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 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: { $0.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解析对象。

+0

我曾考虑过这种方法,但并不认为这值得重复。尝试了其他一些方法后,我认为这看起来目前为止是最好的。感谢您以与问题相同的精神回答问题(并重新开放它)。 – zekel