2017-06-16 45 views
1

如何使用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工具。 –

回答

6

我投票重新開啓自己的問題,因爲雖然它並不方便可行的使用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