2017-06-23 42 views
15

我正在使用帶有JSON數據的Swift 4 Codable協議。我的數據被格式化使得存在在根級別的單個密鑰與含有我需要的屬性,諸如對象值:Swift 4 Codable;如何使用單個根級別密鑰解碼對象

{ 
    "user": { 
    "id": 1, 
    "username": "jdoe" 
    } 
} 

我有一個User結構,可以解碼該user鍵:

struct User: Codable { 
    let id: Int 
    let username: String 
} 

由於idusernameuser性質,而不是在根級別,我需要作出類似於包裝類型,以便:

struct UserWrapper: Codable { 
    let user: User 
} 

然後我可以通過UserWrapper解碼JSON,並且User也被解碼。它看起來像很多冗餘的代碼,因爲我需要每種類型的額外包裝。有沒有辦法避免這種包裝模式或更正確/優雅的方式來處理這種情況?

+0

我並沒有深入研究'Codable'協議,但我認爲最快的方法是直接用內部字典初始化'User'對象。你可以從用戶字段中通過從字典中訪問'userDicitonary'嗎? –

+1

如果您的JSON只包含單個用戶的數據,您是否真的需要用戶密鑰和用戶字典作爲值?僅僅擁有用戶字典是不夠的?上下文不應該表明JSON描述了一個用戶? – Palle

+1

@Palle不幸的是,你並不總是選擇你需要處理的數據格式。 –

回答

19

您可以解碼使用字典:用戶組合,然後提取出用戶對象。例如

struct User: Codable { 
    let id: Int 
    let username: String 
} 

let decoder = JSONDecoder() 
let userDictionary = try decoder.decode([String: User].self, from: jsonData) 
+1

不錯的解決方案!它展示了這些解碼/編碼Swift 4 API真的有多靈活;) –

+1

非常聰明!我的想法是在類和結構上,所以我不會想到它會成爲一本字典!我標記這個解決方案是正確的,因爲它可能是最普遍有用的答案,但Paulo和Rob的解決方案也非常好。稍微冗長一些,但是更加獨立。謝謝! –

+0

@JoshuaBreeden順便說一句,沒有奧利解決方案按預期工作?我仍然在運行Xcode 8,所以我無法自己測試它( –

4

當然,你總是可以實現自定義的解碼/編碼 - 但是對於這個簡單的場景您的包裝類型是一個更好的解決方案海事組織)

相比之下,定製解碼是這樣的:

struct User { 
    var id: Int 
    var username: String 

    enum CodingKeys: String, CodingKey { 
     case user 
    } 

    enum UserKeys: String, CodingKey { 
     case id, username 
    } 
} 

extension User: Decodable { 
    init(from decoder: Decoder) throws { 
     let values = try decoder.container(keyedBy: CodingKeys.self) 

     let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user) 
     self.id = try user.decode(Int.self, forKey: .id) 
     self.username = try user.decode(String.self, forKey: .username) 
    } 
} 

如果您還想支持編碼,您仍然需要遵守Encodable協議。正如我之前所說,您的簡單UserWrapper是更容易;)

22

奧利的答案肯定是去這種情況下,最好的辦法,但它確實推動了一些知識到調用,這可能是不可取的。它也不是很靈活。我仍然認爲這是一個很好的答案,正是你想要的,但這是探索定製結構編碼的一個很好的簡單例子。

我們怎樣才能使這項工作正確:

let user = try? JSONDecoder().decode(User.self, from: json) 

我們不能使用默認的不合格了。我們必須建立我們自己的解碼器。這有點乏味,但並不困難。首先,我們需要對結構編碼成CodingKeys:

struct User { 
    let id: Int 
    let username: String 

    enum CodingKeys: String, CodingKey { 
     case user // The top level "user" key 
    } 

    // The keys inside of the "user" object 
    enum UserKeys: String, CodingKey { 
     case id 
     case username 
    } 
} 

這樣,我們可以通過拉出嵌套集裝箱手工解碼User

extension User: Decodable { 
    init(from decoder: Decoder) throws { 

     // Extract the top-level values ("user") 
     let values = try decoder.container(keyedBy: CodingKeys.self) 

     // Extract the user object as a nested container 
     let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user) 

     // Extract each property from the nested container 
     id = try user.decode(Int.self, forKey: .id) 
     username = try user.decode(String.self, forKey: .username) 
    } 
} 

但我絕對做奧利的方式對於這個問題。

關於這方面的更多信息,請參閱Encoding and Decoding Custom Types

+1

正在尋找其他東西,但是這個答案告訴我nestedContainer這是解決方案對我的問題!謝謝你! –

+0

對於更復雜的jsons很好的答案! –

+0

嗨,Rob,我已經發布了一個關於多個容器的問題,很想聽聽你的想法https:// stacko verflow.com/questions/48410909/swift-4-decodable-multiple-containers謝謝! –

3

我爲Codable創建了一個幫助器擴展,它可以讓事情變得更簡單。

看到https://github.com/evermeer/Stuff#codable

這樣,您可以像這樣創建用戶對象的實例:

let v = User(json: json, keyPath: "user") 

你不必在你原來的用戶結構改變任何東西,你不需要一個包裝。

相關問題