如何使用 Swift Codable 处理部分动态 JSON?
Posted
技术标签:
【中文标题】如何使用 Swift Codable 处理部分动态 JSON?【英文标题】:How to handle partially dynamic JSON with Swift Codable? 【发布时间】:2018-06-09 00:26:29 【问题描述】:我收到了一些通过 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]
然后在type
上的switch
将字典转换为适当的结构。这行得通吗?
【问题讨论】:
检查这个文件:github.com/iwheelbuy/VK/blob/master/VK/Core/Object/Attachment/… 仅供参考,您的 JSON 示例在type
、data
、name
、x
和 y
周围缺少引号...
【参考方案1】:
我不会依赖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的时候真的不知道类型,那么你只需要写一个init(coder:)
先解析type
,然后根据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)
【讨论】:
感谢您的详细回复。问题是我不知道传入消息的类型。它不是基于请求/响应,而是基于 websocket 连接。消息本身都具有type
和data
键。
那你需要写一个init(coder:)
,先解析type
,再解析data
为对应的类型。请参阅上面的扩展答案。
再次感谢!新年快乐!它可以工作,并且我在 ios golang js 之间建立了稳定的 websocket 通信。但是我的payload
在最后的if
子句中,如果总是Payload
类型。因此我不能做类似的事情(如果是Location
)print(payload.x)
。如何获取真实类型而不是协议?
if let person = payload as? Person ...
或 if let location = payload as? Location ...
请对此进行调查并提出您的意见。 ***.com/questions/49181230/…以上是关于如何使用 Swift Codable 处理部分动态 JSON?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Codable 在 Swift 中使用动态文件名解析 JSON
Swift 根据响应类型为 JSON 字段值动态选择 Codable 结构