本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
前言
JSON转Model,明明是个机械活,但是有的时候后台给的接口文档,就是转不出正确的模型,是一件让人非常窝火的事情。
错无非两种,要么后台文档给的有错,要么自己写的有错。
在经受过多次跳坑与爬坑的经历后,我变秃了,也变强了。
今天说说我遇见最讨厌的一种JSON,就是JSON中嵌套JSON String。
后台文档的不给力
事情一开始是这样的,后台给我文档,说请求响应的JSON长这个样子:
{
"result":true,
"body":{
"ret_code":"0",
"ret_msg":"成功",
"serial_number":"20211002115646portal511788",
"timestamp":"2021-10-02 03:56:46",
"response_data":{
"token":"0765499c-8643-4491-8c8e-50f92a2ea004",
"expiredMills":1633751806616
}
}
}
我心想,这有何难,工具一键生成代码,请求转换就好了嘛:
// MARK: - Token
struct Token: Codable {
let result: Bool?
let body: Body?
}
// MARK: - Body
struct Body: Codable {
let retCode: String?
let retMsg: String?
let serialNumber: String?
let timestamp: String?
let responseData: ResponseData?
enum CodingKeys: String, CodingKey {
case retCode = "ret_code"
case retMsg = "ret_msg"
case serialNumber = "serial_number"
case timestamp = "timestamp"
case responseData = "response_data"
}
}
// MARK: - ResponseData
struct ResponseData: Codable {
let token: String?
let expiredMills: Int?
}
由于项目比较老,还是用的Alamofire4,请求与转换:
func getToken() {
Alamofire.request("请求网址", method: .post).responseData { response in
switch response.result {
case .success(let data):
guard let model = try? JSONDecoder().decode(Token.self, from: data) else {
return
}
print(model)
case .failure(_):
break
}
}
}
然后就在.success中的guard let
这里return了,我反复看了文档,然后又看了定义的Model,感觉没错啊,也走到success里了,data也有值啊,怎么回事呢?
所幸先把data转成Dictionary看看再说,顺带在postman里请求看看,JSON到底是啥:
{
"result": true,
"body": "{\"ret_code\":\"0\",\"ret_msg\":\"成功\",\"serial_number\":\"20211002115646portal511788\",\"timestamp\":\"2021-10-02 03:56:46\",\"response_data\":{\"token\":\"0765499c-8643-4491-8c8e-50f92a2ea004\",\"expiredMills\":1633751806616}}"
}
结果这个一个JSON中包裹JSON String。
JSON中包裹JSON String解析
这种JSON中包裹JSON String解析非常的麻烦:
-
首先Model的结构要重新定义。
-
要多写一些代码去做处理。
然后大概就变成了这样,这里贴出关键代码:
struct Token: Codable {
let result: Bool?
/// body变成了String
let body: String?
}
转换多了几步:
guard let model = try? JSONDecoder().decode(Token.self, from: data) else {
return
}
guard let string = model.body else {
return
}
guard let stringData = string.data(using: .utf8) else {
return
}
guard let body = try? JSONDecoder().decode(Body.self, from: stringData) else {
return
}
-
首先转成Token模型。
-
再拿Token实例中的body,这个body是个String。
-
然后body转为stringData。
-
最后stringData最后转成Body的Model。
你说累不累?
看着这个头大的转换,我就想有没有更简洁的方式。
注意,这里其实可以将多个guard写成一个guard,不断let即可,我没有这么写是为了方便调试。
Response全部转String,再转Data,最后转Model的尝试
我想到了Alamofire中有一个responseString
方法,我想到了这样一个思路:
graph TD
Response --> String --> Data --> Model
于是我试了试:
struct Token: Codable {
let result: Bool?
/// body试着用Body去接收
let body: Body?
}
func getToken() {
Alamofire.request("请求网址", method: .post).responseString( encoding: .utf8) { res in
switch res.result {
case .success(let string):
guard let stringData = string.data(using: .utf8) else {
return
}
guard let model = try? JSONDecoder().decode(Token.self, from: stringData) else {
return
}
print(model)
case .failure(_):
break
}
}
}
不过尝试的结果是让人失望了的,在这一步就return
了:
guard let model = try? JSONDecoder().decode(Token.self, from: stringData) else {
return
}
转换成为响应体中的String没有问题,String转Data也没有问题,但是Data转Model就出错了。
所以说这种JSON中包裹JSON String的返回真的很难办啊!
最好的方式其实是和后台沟通
既然自己App端干这JSON转Model的事情吃力不讨好,最快最简洁的方式就是和后台沟通,让他们把这个JSON String转成JSON再塞到这个母JSON中,可以采取的措施有以下几点:
-
后台给出的文档与实际的返回有出入,要么改文档,要么改Response。可是一般程序猿不喜欢改文档。
-
返回JSON String不合符Response返回的规范,也不安全。
-
拉着Android端的同学一起去找后台,人多力量大。
-
反馈到技术经理,让他去定夺。
-
不要相信后台说后续优化,要么现在改,要么就不改,否则自己下次还是得删改代码。
总结
上面吧嗒吧嗒说了一大堆,怎么处理JSON啊,各种思路啊。
其实归结到最后,如果能和后台沟通好这件事情,自己可能处理起来就会轻松不少。程序猿有的时候只会站在自己的视角去解决问题,有的时候,跳出来,弄清楚原因找到根节,事情就会变得简单不少。
我们下期见。
欢迎在评论区讨论,掘金官方将在掘力星计划活动结束后,在评论区抽送100份掘金周边,抽奖详情见活动文章。