json - 如何使用 JSONDecoder 解码未知类型的 JSON?
问题描述
这是我的场景:我有一个快速的 WebSocket 服务器和一个 Javascript 客户端。在同一个 WebSocket 上,我将发送对应于不同Codable 类型的各种对象。如果知道正确的类型,则解码很简单。对我来说,困难在于确定客户端发送的是哪种类型。我的第一个想法是使用如下所示的 JSON:
{type: "GeoMarker",
data: {id: "2",
latitude: "-97.32432"
longitude: "14.35436"}
}
这样我就知道data
使用let marker = try decoder.decode(GeoMarker.self)
这似乎很简单进行解码,但由于某种原因,我无法弄清楚如何将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
推荐阅读
- r - 如何使用 R 或 Python(RVEST、HTTR、XHR 或类似的东西)刮取本地存储 KEY/VALUES
- latex - 乳胶中的@符号是什么意思
- java - 在输入EditText并保存到Android中的数组时通过拆分功能分隔数字
- angular - Angular firebase 应用程序中的警告“看起来您正在使用 Firebase JS SDK 的开发版本”
- python - 使用python在Zapier中的字符串中的字符之间提取数据
- node.js - 如何在 mocha-chai 测试中检查数组的所有元素是否具有子属性?
- haskell - 尽管在使用检查器测试 Monoid 定律时定义了任意实例,但没有任意实例
- azure - 为什么在 Azure 中首次提供 js bundle 时响应时间很慢?
- animation - gnuplot 中的 animate2D
- java - 使用 Java Apache HttpClient 4.5.12 时如何解决“连接重置”