項(xiàng)目上有一個(gè)需求径玖,把一個(gè)h5頁(yè)面改造為原生app媒区,但接口和json仍使用原來(lái)的锚烦。
但json的格式對(duì)iOS非常不友好
它的結(jié)構(gòu)可能是這樣的
{
"A": "呵呵",
"B": "哈哈",
"C": "CCC",
"路人甲": "123"
}
這樣的
{
"A": "呵呵",
"B": ["哈哈"],
"C": {
"height": "10cm",
"width": "5cm"
},
"路人甲": {
"info": {
"年齡": 18,
"性別": "男"
}
}
}
甚至這樣的
{
"A": "呵呵",
"B": ["哈哈"],
"C": {
"height": "10cm",
"width": "5cm"
},
"路人甲": {
"info": {
"年齡": 48,
"性別": "男"
},
"children": {
"路人乙": {
"info": {
"年齡": 22,
"性別": "男"
}
},
"路人丙": {
"info": {
"年齡": 28,
"性別": "女"
},
"children": {
"小明": {
"info": {
"年齡": 4,
"性別": "男"
}
}
}
}
}
}
}
孩子那個(gè)看不懂扩然?可以看看下面這個(gè)艘儒,也是類似的結(jié)構(gòu)。
{
"一級(jí)菜單": {
"code": "1",
"data": {
"二級(jí)菜單1": {
"code": "100",
"data": {
"三級(jí)菜單11": {
"code": "10000"
}
}
},
"二級(jí)菜單2": {
"code": "101",
"data": {
"三級(jí)菜單21": {
"code": "10100"
},
"三級(jí)菜單22": {
"code": "10101"
}
}
}
}
}
}
數(shù)了數(shù)夫偶,有好幾個(gè)坑界睁。
- 字典的key帶有中文
- 字典的value類型不確定,可能是int, 可能是string兵拢,可能是數(shù)組翻斟,還有可能又是一個(gè)同樣的字典,這個(gè)結(jié)構(gòu)也許可以無(wú)限循環(huán)下去说铃。
- 因?yàn)樾枨笮枰鶕?jù)這個(gè)json來(lái)展示分級(jí)菜單访惜,但菜單名存儲(chǔ)在key里面嘹履,并且同級(jí)菜單不是存在一個(gè)數(shù)組里。
其實(shí)這個(gè)最好的解決方案是在字典里加一個(gè)name字段债热,然后把原來(lái)的字典改成數(shù)組砾嫉。(無(wú)奈)
當(dāng)然我看到這個(gè)問(wèn)題首先肯定是去問(wèn)領(lǐng)導(dǎo),這個(gè)能不能讓后端改一下json
然而被否決了窒篱。
而且所有的數(shù)據(jù)解析都要求用Model來(lái)處理焕刮,還不讓直接用字典
這個(gè)字典轉(zhuǎn)模型難度實(shí)在是有點(diǎn)大。
但是最終還是解決了
下面步驟:
- key帶中文問(wèn)題
用JSONDecoder把字典轉(zhuǎn)model墙杯,字典的key必須和model的屬性一樣配并,而屬性不能用中文
但我們可以使用CodingKeys更改他們的對(duì)應(yīng)關(guān)系。
struct AModel: Codable {
var A: String?
var B: String?
var C: String?
var someone: String?
enum CodingKeys: String, CodingKey {
case someone = "路人甲"
case A
case B
case C
}
}
這樣在轉(zhuǎn)換的時(shí)候 路人甲的數(shù)據(jù)就可以存到someone里去了
- value類型不確定
這個(gè)我們可以使用枚舉的高級(jí)用法——關(guān)聯(lián)值(Associated Value)
先上代碼
enum TestModelEnum: Codable {
case int(Int)
case string(String)
case stringArray([String])
case dictionary([String: String])
case modelDict([String: TestModelEnum])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Int.self) {
self = .int(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode([String].self) {
self = .stringArray(x)
return
}
if let x = try? container.decode([String: String].self) {
self = .dictionary(x)
return
}
if let x = try? container.decode([String: TestModelEnum].self) {
self = .modelDict(x)
return
}
throw DecodingError.typeMismatch(TestModelEnum.self,
DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "Wrong type for TestModelEnum"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
case .stringArray(let x):
try container.encode(x)
case .dictionary(let x):
try container.encode(x)
case .modelDict(let x):
try container.encode(x)
}
}
}
我們把所有可能出現(xiàn)的類型都寫在了case里霍转,而且由于字典里還可能再嵌套字典荐绝,所以我的枚舉類型里包含了自身
在init方法中會(huì)去嘗試轉(zhuǎn)換成各種類型,轉(zhuǎn)換失敗會(huì)去嘗試另一種避消,需要注意的是低滩,如果你的類型是另一個(gè)Model, 而且這個(gè)Model的所有屬性都是可選的(optional),但實(shí)際上你這里的數(shù)據(jù)可能只是一個(gè)String岩喷,轉(zhuǎn)換會(huì)成功但Model里面的值都為nil【一定要失敗才能繼續(xù)往下進(jìn)行恕沫,可以控制優(yōu)先級(jí)或者去掉optional】
- 不止要轉(zhuǎn)換成功,還要把key作為信息保存起來(lái)
先從那個(gè)菜單的開(kāi)始纱意,那個(gè)結(jié)構(gòu)相對(duì)簡(jiǎn)單一點(diǎn)
我們先寫一個(gè)基本類型婶溯,這是每級(jí)菜單里的信息
struct MenuModel: Codable {
var code: String?
var data: [String: MenuModel]?
}
再寫一個(gè)AllMenuModel用來(lái)轉(zhuǎn)存MenuModel的數(shù)據(jù)
struct AllMenuModel: Codable {
var values: [MenuModel] = []
var keys: [String] = []
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode([String: MenuModel].self) {
for item in x {
keys.append(item.key)
values.append(item.value)
}
return
}
throw DecodingError.typeMismatch(MenuModel.self,
DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "Wrong type for MenuModel"))
}
}
測(cè)試方法
func test() {
let json = [
"一級(jí)菜單": [
"code": "1",
"data": [
"二級(jí)菜單1": [
"code": "100",
"data": [
"三級(jí)菜單11": [
"code": "10000"
]
]
],
"二級(jí)菜單2": [
"code": "101",
"data": [
"三級(jí)菜單21": [
"code": "10100"
],
"三級(jí)菜單22": [
"code": "10101"
]
]
]
]
]
] as [String: Any]
let model = try? JSONDecoder().decode(AllMenuModel.self, from: JSONSerialization.data(withJSONObject: json, options: []))
print(model)
}
輸出結(jié)果:
Optional(DecoderTest.AllMenuModel(values: [DecoderTest.MenuModel(code: Optional("1"), data: Optional(["二級(jí)菜單2": DecoderTest.MenuModel(code: Optional("101"), data: Optional(["三級(jí)菜單22": DecoderTest.MenuModel(code: Optional("10101"), data: nil), "三級(jí)菜單21": DecoderTest.MenuModel(code: Optional("10100"), data: nil)])), "二級(jí)菜單1": DecoderTest.MenuModel(code: Optional("100"), data: Optional(["三級(jí)菜單11": DecoderTest.MenuModel(code: Optional("10000"), data: nil)]))]))], keys: ["一級(jí)菜單"]))
轉(zhuǎn)換成功了,并且key被我們存到了AllMenuModel里以供使用偷霉。
當(dāng)然迄委,也可以寫一個(gè)BaseModel基類,里面帶一個(gè)屬性key类少,這樣key和value可以在同一個(gè)model里叙身。
我這里就不寫了。
這個(gè)問(wèn)題解決了硫狞,和第二個(gè)解決方案組合一下就可以解決路人甲的那個(gè)問(wèn)題了信轿。
但是要解決那個(gè)問(wèn)題,又得寫一個(gè)類似AllMenuModel的結(jié)構(gòu)體残吩,會(huì)不會(huì)太麻煩了财忽。
于是我對(duì)這里的代碼做了一些改進(jìn)。
其實(shí)他們的區(qū)別只在于values的類型不一樣泣侮,把類型做成泛型就好了即彪。
public struct KeyValueDictionary<T: Codable>: Codable {
var values: [T] = []
var keys: [String] = []
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode([String: T].self) {
for item in x {
keys.append(item.key)
values.append(item.value)
}
return
}
throw DecodingError.typeMismatch(KeyValueDictionary.self,
DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "Wrong type for KeyValueDictionary"))
}
}
enum TestModelEnum: Codable {
case int(Int)
case string(String)
case stringArray([String])
case dictionary([String: String])
case info(KeyValueDictionary<[String: TestModelEnum]>)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Int.self) {
self = .int(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode([String].self) {
self = .stringArray(x)
return
}
if let x = try? container.decode([String: String].self) {
self = .dictionary(x)
return
}
if let x = try? container.decode(KeyValueDictionary<[String: TestModelEnum]>.self) {
self = .info(x)
return
}
throw DecodingError.typeMismatch(TestModelEnum.self,
DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "Wrong type for TestModelEnum"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
case .stringArray(let x):
try container.encode(x)
case .dictionary(let x):
try container.encode(x)
case .info(let x):
try container.encode(x)
}
}
}
這樣,上面提到的坑全部都解決了活尊。