![](/img/trans.png)
[英]How to handle JSON format inconsistencies with Swift 4 Codable?
[英]How to handle partially dynamic JSON with Swift Codable?
我有一些通過websocket連接進入的JSON消息。
// sample message
{
type: "person",
data: {
name: "john"
}
}
// some other message
{
type: "location",
data: {
x: 101,
y: 56
}
}
如何使用Swift 4和Codable協議將這些消息轉換為正確的結構?
在Go中我可以做類似的事情:“嘿,此刻我只關心type
字段,而我對其余部分( data
部分)不感興趣。” 它看起來像這樣
type Message struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
如您所見, Data
的類型為json.RawMessage
,稍后可以對其進行解析。 這是一個完整的示例https://golang.org/pkg/encoding/json/#example_RawMessage_unmarshal 。
我可以在Swift中做類似的事情嗎? 喜歡(尚未嘗試過)
struct Message: Codable {
var type: String
var data: [String: Any]
}
然后switch
type
以將字典轉換為適當的結構。 那會有用嗎?
我不會依賴Dictionary
。 我會使用自定義類型。
例如,讓我們假設:
你知道你要回來的對象(因為請求的性質); 和
除了data
內容之外,這兩種類型的響應確實返回相同的結構。
在這種情況下,您可以使用一個非常簡單的通用模式:
struct Person: Decodable {
let name: String
}
struct Location: Decodable {
let x: Int
let y: Int
}
struct ServerResponse<T: Decodable>: Decodable {
let type: String
let data: T
}
然后,當您想要解析一個Person
的響應時,它將是:
let data = json.data(using: .utf8)!
do {
let responseObject = try JSONDecoder().decode(ServerResponse<Person>.self, from: data)
let person = responseObject.data
print(person)
} catch let parseError {
print(parseError)
}
或者解析一個Location
:
do {
let responseObject = try JSONDecoder().decode(ServerResponse<Location>.self, from: data)
let location = responseObject.data
print(location)
} catch let parseError {
print(parseError)
}
可以接受更復雜的模式(例如,基於它遇到的type
值動態解析data
類型),但除非必要,否則我不傾向於追求這樣的模式。 這是一種很好的,簡單的方法,它可以實現典型模式,您可以在其中了解特定請求的相關響應類型。
如果您希望可以使用從data
值解析的內容來驗證type
值。 考慮:
enum PayloadType: String, Decodable {
case person = "person"
case location = "location"
}
protocol Payload: Decodable {
static var payloadType: PayloadType { get }
}
struct Person: Payload {
let name: String
static let payloadType = PayloadType.person
}
struct Location: Payload {
let x: Int
let y: Int
static let payloadType = PayloadType.location
}
struct ServerResponse<T: Payload>: Decodable {
let type: PayloadType
let data: T
}
然后,您的parse
函數不僅可以解析正確的data
結構,還可以確認type
值,例如:
enum ParseError: Error {
case wrongPayloadType
}
func parse<T: Payload>(_ data: Data) throws -> T {
let responseObject = try JSONDecoder().decode(ServerResponse<T>.self, from: data)
guard responseObject.type == T.payloadType else {
throw ParseError.wrongPayloadType
}
return responseObject.data
}
然后你可以像這樣稱呼它:
do {
let location: Location = try parse(data)
print(location)
} catch let parseError {
print(parseError)
}
這不僅返回Location
對象,還驗證服務器響應中type
的值。 我不確定這是值得的,但如果你想這樣做,那就是一種方法。
如果在處理JSON時你真的不知道類型,那么你只需要編寫一個首先解析該type
的init(coder:)
,然后根據包含的type
的值解析data
:
enum PayloadType: String, Decodable {
case person = "person"
case location = "location"
}
protocol Payload: Decodable {
static var payloadType: PayloadType { get }
}
struct Person: Payload {
let name: String
static let payloadType = PayloadType.person
}
struct Location: Payload {
let x: Int
let y: Int
static let payloadType = PayloadType.location
}
struct ServerResponse: Decodable {
let type: PayloadType
let data: Payload
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try values.decode(PayloadType.self, forKey: .type)
switch type {
case .person:
data = try values.decode(Person.self, forKey: .data)
case .location:
data = try values.decode(Location.self, forKey: .data)
}
}
enum CodingKeys: String, CodingKey {
case type, data
}
}
然后你可以做以下事情:
do {
let responseObject = try JSONDecoder().decode(ServerResponse.self, from: data)
let payload = responseObject.data
if payload is Location {
print("location:", payload)
} else if payload is Person {
print("person:", payload)
}
} catch let parseError {
print(parseError)
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.