JSON in Swift4

记录使用 SwiftyJSON 和 HandyJSON 在 Swift 4 中处理 JSON 文件的过程

最近接了一个项目,这个项目用到了中央天气预报 API的接口,这个接口提供了其所用城市码的数据文件,我采用了 JSON 文件来处理数据信息。

在进行 Swift 的 JSON 文件处理时,我查阅了大量资料,大多数的博客均采用了以下的类来读取 JSON 文件并转换为Array

  • NSBundle
  • NSData
  • NSJSONSerialization

存在了问题

参考他人博客的处理方式,我在自己的平台上测试时,发现会出现以下的"神秘"异常

嗯,嗯?NSBundle呢??上下翻看发现NSBundle并没有出现在匹配列表中。

这个情况我不考虑引入问题,而是考虑 Swift 3 到 Swift 4 的差异,于是我查找了 Xcode 中的 Develop Documentation

哈!果然没有第一个出现,说明这个类现在已经被改成别的名称了(顺势吐槽不是说好的 NS 公司的产物吗居然改名??)

然后把 NS 去掉,看看结果……

Foundation??Bundle??右边有个链接查看 Objective-C 的,点一下

绝了,还真是 Swift 和 Objective-C 两个版本不同名的

找到问题,出个解法

既然改名叫了Bundle,那其他的两个应该也没差了吧,果断写出下面的代码来试试

1
2
3
4
5
// JSON文件名: CityCode.json
let path: String! = Bundle.main.path(forResource: "CityCode", ofType: "json")
let nsData: NSData! = NSData(contentsOfFile: path)
let data: Data! = Data(referencing: nsData!)
let json = try? JSON(data: data)

这里为什么不直接用Data(contentsOf: URL),别问为什么,有一个更容易看懂的NSData(contentsOfFile: String)和一个Data(referencing: NSData),懒得用NSURL

好吧好吧还是给一个 URL 版的

1
2
3
4
let path: String! = Bundle.main.path(forResource: "CityCode", ofType: "json")
let url: URL! = NSURL.fileURL(withPath: path)
let data: Data! = try? Data(contentsOf: url)
let json = try? JSON(data: data)

SwiftyJSON 和 HandyJSON

细心的话你会注意到其中的JSON(data: data)class City: HandyJSON

JSON(data: data)

这是 SwiftyJSON 创建 JSON 对象的方法,使用由Data类解析 JSON 文件获得的数据

class City: HandyJSON

这是使City类能够支持 JSON to Model 的方式,通过使用 HandyJSON 框架,为City类提供反序列化能力,并通过下面的代码将 JSON 文件转化成Array

1
cityArray.append(JSONDeserializer<City>.deserializeFrom(json: value.rawString())!)

应用范例

这里给出中央天气接口城市数据的 JSON 部分内容,以及我个人封装的城市对象 Model,和完整的 CityUtils 代码

CityCode.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[
    {
        "ID": "1",
        "cityName": "北京",
        "cityEN": "Beijing",
        "townID": "CHBJ000000",
        "townName": "北京",
        "townEN": "Beijing"
    },
    {
        "ID": "2",
        "cityName": "北京",
        "cityEN": "Beijing",
        "townID": "CHBJ000100",
        "townName": "海淀",
        "townEN": "Haidian"
    }
]

Weather.swift

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
/// 天气API的基础类, 继承HandyJSON
class City: HandyJSON {

    var ID: String
    var cityName: String
    var cityEN: String
    var townID: String
    var townName: String
    var townEN: String

    required convenience init() {
        self.init(ID: "", cityName: "", cityEN: "", townID: "", townName: "", townEN: "")
    }

    init(ID: String, cityName: String, cityEN: String, townID: String, townName: String, townEN: String) {
        self.ID = ID
        self.cityName = cityName
        self.cityEN = cityEN
        self.townID = townID
        self.townName = townName
        self.townEN = townEN
    }

    var description: String {
        return self.toJSONString() ?? "[]"
    }

}

/// 城市工具类
///
/// 城市信息通过CityCode.json获取
class CityUtils {

    /// 城市工具的单例
    static let instance = CityUtils()

    /// 从JSON数据库文件获取的城市列表
    private var cityArray: [City]

    /// 访问城市列表
    var cities: [City] {
        return cityArray
    }

    /// 私有的初始化方法
    private init() {
        cityArray = []
        // NSData style
        // let path: String! = Bundle.main.path(forResource: "CityCode", ofType: "json")
        // let nsData: NSData! = NSData(contentsOfFile: path)
        // let data: Data! = Data(referencing: nsData!)
        // let json = try? JSON(data: data)

        // URL style
        let path: String! = Bundle.main.path(forResource: "CityCode", ofType: "json")
        let url: URL! = NSURL.fileURL(withPath: path)
        let data: Data! = try? Data(contentsOf: url)
        let json = try? JSON(data: data)
        for (_, value) in json! {
            cityArray.append(JSONDeserializer<City>.deserializeFrom(json: value.rawString())!)
        }
    }

    /// 通过ID取得单个城市对象
    func get(byID id: String) -> City? {
        for city in cityArray {
            if city.ID == id {
                return city
            }
        }
        return nil
    }

    /// 通过区域ID获取单个城市对象, 另外区域ID可以直接用于请求API
    func get(byTownID townID: String) -> City? {
        for city in cityArray {
            if city.townID == townID {
                return city
            }
        }
        return nil
    }

    /// 通过城市名称获取该城市下的区域列表
    func list(byCityName cityName: String) -> [City] {
        var list: [City] = []
        for city in cityArray {
            if city.cityName == cityName {
                list.append(city)
            }
        }
        return list
    }

    /// 通过城市英文名称获取该城市下的区域列表
    func list(byCityEN cityEN: String) -> [City] {
        var list: [City] = []
        for city in cityArray {
            if city.cityEN == cityEN {
                list.append(city)
            }
        }
        return list
    }

}