Swift Codable解析Json的一种极端情况

1,653 阅读2分钟

如下的Json

{
    "code": 0,
    "msg": "",
    "data": {
        "fields": [
            "ts_code",
            "trade_date",
            "open"
        ],
        "items": [
            [
                "000001.SZ",
                "20210326",
                20.84
            ],
            [
                "600000.SH",
                "20210326",
                10.68
            ]
        ],
        "has_more": false
    }
}

解析会遇到两个问题:

  1. 如何把分离的Key和value组合 (key在fields里,value在items里)
  2. 如何处理数组中不同的类型 (items里的数据类型有String和Float两种)

当然这段json对我来说也很头痛,花了不少时间处理,如果是同事写的,我会锤死他之前让他改一下结构

使用Swift提供的Codable解析json是我觉得最优雅的方案,无需赋值过程。同时能够享受到来自底层的不断优化,比SwiftJson要舒服一些,当然你喜欢用什么都可以,接下来研究一下如何解析

最终代码

import Foundation

typealias Fields = Array<String>
typealias Items =  Array<Array<MetadataType>>

extension Data {
    /// json data 转模型
    /// 可以直接拿网络请求成功的response直接转换
    func jsonDataMapModel<T: Decodable>(_ type: T.Type) -> T? {
        let decoder = JSONDecoder()
        do {
            return try decoder.decode(T.self, from: self)
        } catch {
            print("解析失败\(self)")
            return nil
        }
    }
}

extension String {
    /// json字符串转模型
    func jsonStringMapModel<T: Decodable>(_ type: T.Type) -> T? {
        
        if let jsonData = self.data(using: .utf8) {
            return jsonData.jsonDataMapModel(T.self)
        }
        print("解析失败\(self)")
        return nil
    }
}

enum MetadataType: Codable {
  case float(Float)
  case string(String)

  init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    do {
        self = try .float(container.decode(Float.self))
    } catch DecodingError.typeMismatch {
      do {
        self = try .string(container.decode(String.self))
      } catch DecodingError.typeMismatch {
        throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
      }
    }
  }

  func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    switch self {
    case .float(let float):
      try container.encode(float)
    case .string(let string):
      try container.encode(string)
    }
  }
}

//3层 Model定义

struct WDTRootModel<T: Decodable>: Decodable {

    var code:Int? = nil

    var msg:String? = nil

    var data:WDTDataModel<T>? = nil
}

struct WDTDataModel<T: Decodable>: Decodable {
    var fields: Fields? = nil
    var items: Items? = nil
    //方案核心代码 使用计算属性来解决数据聚合的问题,使用泛型解决数组中多类型的问题
    var realItems:[T?]?{
        items!.map { (values) -> T? in
            let dic = Dictionary.init(uniqueKeysWithValues: zip(fields!, values.map({ (md) -> Any in
                switch md {
                case .float(let res):
                    return res
                case .string(let res):
                    return res
                }
            })))
            if let data = try? JSONSerialization.data(withJSONObject: dic, options: []) {
                return data.jsonDataMapModel(T.self)
            }
            return nil
        }
    }
}

//最终目标结构
struct DailyModel: Decodable {
    var ts_code:String? = nil
    var trade_date:String? = nil
    var open:Float? = nil
}

测试代码:

func test(){
    let jsonString = """
    {
        "code": 0,
        "msg": "",
        "data": {
            "fields": [
                "ts_code",
                "trade_date",
                "open",
                "high",
                "low",
                "close",
                "pre_close",
                "change",
                "pct_chg",
                "vol",
                "amount"
            ],
            "items": [
                [
                    "000001.SZ",
                    "20210326",
                    20.84,
                    21.4,
                    20.76,
                    21.14,
                    20.75,
                    0.39,
                    1.8795,
                    822108.06,
                    1733412.579
                ],
                [
                    "600000.SH",
                    "20210326",
                    10.68,
                    10.73,
                    10.61,
                    10.62,
                    10.63,
                    -0.01,
                    -0.0941,
                    408827.34,
                    435739.848
                ]
            ],
            "has_more": false
        }
    }
    """
    let model = jsonString.jsonStringMapModel(WDTRootModel<DailyModel>.self)
    print(model?.data?.items?[0] ?? "test无值")
    print(model?.data?.realItems?[0]?.ts_code ?? "name无值")
}

test()

好了,copy到playground里试一下吧