swift 使用Codable 解析復(fù)雜類型

項(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è)坑界睁。

  1. 字典的key帶有中文
  2. 字典的value類型不確定,可能是int, 可能是string兵拢,可能是數(shù)組翻斟,還有可能又是一個(gè)同樣的字典,這個(gè)結(jié)構(gòu)也許可以無(wú)限循環(huán)下去说铃。
  3. 因?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)大。

但是最終還是解決了

下面步驟:

  1. 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里去了

  1. 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】

  1. 不止要轉(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)
        }
    }
}

這樣,上面提到的坑全部都解決了活尊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末祖凫,一起剝皮案震驚了整個(gè)濱河市琼蚯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惠况,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宁仔,死亡現(xiàn)場(chǎng)離奇詭異稠屠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)翎苫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門权埠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人煎谍,你說(shuō)我怎么就攤上這事攘蔽。” “怎么了呐粘?”我有些...
    開(kāi)封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵满俗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我作岖,道長(zhǎng)唆垃,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任痘儡,我火速辦了婚禮辕万,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沉删。我一直安慰自己渐尿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布矾瑰。 她就那樣靜靜地躺著砖茸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪脯倚。 梳的紋絲不亂的頭發(fā)上渔彰,一...
    開(kāi)封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音推正,去河邊找鬼恍涂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛植榕,可吹牛的內(nèi)容都是我干的再沧。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼尊残,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼炒瘸!你這毒婦竟也來(lái)了淤堵?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤顷扩,失蹤者是張志新(化名)和其女友劉穎拐邪,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體隘截,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扎阶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了婶芭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片东臀。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖犀农,靈堂內(nèi)的尸體忽然破棺而出惰赋,到底是詐尸還是另有隱情,我是刑警寧澤呵哨,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布赁濒,位于F島的核電站,受9級(jí)特大地震影響仇穗,放射性物質(zhì)發(fā)生泄漏流部。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一纹坐、第九天 我趴在偏房一處隱蔽的房頂上張望枝冀。 院中可真熱鬧,春花似錦耘子、人聲如沸果漾。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)绒障。三九已至,卻和暖如春捍歪,著一層夾襖步出監(jiān)牢的瞬間户辱,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工糙臼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留庐镐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓变逃,卻偏偏與公主長(zhǎng)得像必逆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容