[英]How to use JSONDecoder to decode JSON with unknown type?
這是我的情況:我有一個快速的WebSocket服務器和一個Javascript客戶端。 通過同一個WebSocket,我將發送對應於不同Codable類型的各種對象。 如果知道正確的類型,則很容易進行解碼。 對我而言,困難在於確定從客戶端發送的是哪種類型。 我的第一個想法是使用類似於以下內容的JSON:
{type: "GeoMarker",
data: {id: "2",
latitude: "-97.32432"
longitude: "14.35436"}
}
這樣,我就知道可以使用let marker = try decoder.decode(GeoMarker.self)
來解碼data
這似乎很簡單,但是由於某些原因,我只是不知道如何將data
對象提取為JSON,因此可以使用GeoMarker類型對其進行解碼。
我想到的另一個解決方案是創建一個中間類型,如下所示:
struct Communication: Decodable {
let message: String?
let markers: [GeoMarker]?
let layers: [GeoLayer]?
}
這樣,我可以使用以下格式發送JSON:
{message: "This is a message",
markers: [{id: "2",
latitude: "-97.32432"
longitude: "14.35436"},
{id: "3",
latitude: "-67.32432"
longitude: "71.35436"}]
}
並使用let com = try decoder.decode(Communication.self)
並解包可選的消息,標記和圖層變量。 這可行,但似乎很笨拙,尤其是當我需要更多類型時。 說完並完成之后,我可能最終需要8-10。
我已經考慮了這一點,但是覺得自己沒有一個令人滿意的解決方案。 會有更好的方法嗎? 我沒有意識到這種事情的標准嗎?
- - 編輯 - -
自然地跟進,在上述相同情況下,您將如何編碼為相同的JSON格式?
作為您的第一個選擇,您可以通過自定義解碼來實現。
首先,為所有可能的數據類型創建一個具有關聯值的枚舉。
struct GeoMarker: Decodable {
let id:Int
let latitude:Double
let longitude:Double
}
enum ResponseData {
case geoMarker(GeoMarker)
case none
}
現在為您的枚舉提供自定義解碼,以解析所有不同類型的數據對象。
extension ResponseData: Decodable{
enum CodingKeys: String, CodingKey {
case type = "type"
case data = "data"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "GeoMarker":
let data = try container.decode(GeoMarker.self, forKey: .data)
self = .geoMarker(data)
default:
self = .none
}
}
}
您可以像這樣使用它...
let json = """
{
"type": "GeoMarker",
"data": {
"id": 2,
"latitude": -97.32432,
"longitude": 14.35436
}
}
"""
let testRes = try? JSONDecoder().decode(ResponseData.self, from: json.data(using: .utf8)!)
if let testRes = testRes {
if case let ResponseData.geoMarker(geoMarker) = testRes {
print("\(geoMarker.id) \(geoMarker.latitude) \(geoMarker.longitude)")
}
}
要實現自定義編碼器,請使用以下內容。
extension ResponseData: Encodable{
func encode(to encoder: Encoder) throws {
let container = encoder.container(keyedBy: CodingKeys.self)
if case let .geoMarker(geoMarker) = self {
try container.encode("Marker", forKey: .type)
try container.encode(geoMarker, forKey: .data)
}
}
您可以像這樣使用它:
let marker = GeoMarker(id:2, latitude: "-97.32432", longitude: "14.35436")
let encoder = JSONEncoder()
let data = try encoder.encode(.geoMarker(marker))
//Send data over WebSocket
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.