更多文章git
在看ObjectMapper源码的时候,我本身尝试着写了一个简易的JSON解析器。代码在DJJSON的ObjMapper里。github
首先咱们都知道,使用ObjectMapper的时候,咱们必定要实现Mappable协议。这个协议里又有两个要实现的方法:json
init?(map: Map)
mutating func mapping(map: Map)
复制代码
使用的时候,只用以下:swift
struct Ability: Mappable {
var mathematics: String?
var physics: String?
var chemistry: String?
init?(map: Map) {
}
mutating func mapping(map: Map) {
mathematics <- map["mathematics"]
physics <- map["physics"]
chemistry <- map["chemistry"]
}
}
复制代码
而后对应的json以下:api
let json = """
{
"mathematics": "excellent",
"physics": "bad",
"chemistry": "fine"
}
"""
复制代码
而后将这段json解析为Ability的Model, 即:app
let ability = Mapper<Ability>().map(JSONString: json)
复制代码
这样就完成了JSON数据的解析成Model的过程,这也是咱们项目中最频繁出现的场景。那么,咱们有想过,为何这样就能将JSON的数据转化为对应的Model么?为何会须要有‘<-’这么奇怪的符号,它又是啥意思呢?源码分析
首先初看<-的符号,咱们的第一感受就是把右边的值赋给左边的变量,而后咱们去看源码,发现这个符号是这个库自定义的一个操做符。在Operators.swift里。学习
定义以下:url
infix operator <-
/// Object of Basic type
public func <- <T>(left: inout T, right: Map) {
switch right.mappingType {
case .fromJSON where right.isKeyPresent:
FromJSON.basicType(&left, object: right.value())
case .toJSON:
left >>> right
default: ()
}
}
复制代码
而后根据不一样的泛型类型,这个操做符会进行不一样的处理。spa
接着,咱们再看一下map方法。
map方法存在于Mapper类中, 定义以下:
func map(JSONString: String) -> M? {
if let JSON = Mapper.parseJSONString(JSONString: JSONString) as? [String: Any] {
return map(JSON: JSON)
}
return nil
}
func map(JSON: [String: Any]) -> M? {
let map = Map(JSON: JSON)
if let klass = M.self as? Mappable.Type {
if var obj = klass.init(map: map) as? M {
obj.mapping(map: map)
return obj
}
}
return nil
}
复制代码
能够看到,在map的方法中,咱们最后会调用Mappable协议中定义的mapping方法,来对json数据作出转化。
最后再看一下Map这个类,这个类主要用来处理找到key所对应的value。处理方式以下:
private func valueFor(_ keyPathComponents: ArraySlice<String>, dict: [String: Any]) -> (Bool, Any?) {
guard !keyPathComponents.isEmpty else { return (false, nil) }
if let keyPath = keyPathComponents.first {
let obj = dict[keyPath]
if obj is NSNull {
return (true, nil)
} else if keyPathComponents.count > 1, let d = obj as? [String: Any] {
let tail = keyPathComponents.dropFirst()
return valueFor(tail, dict: d)
} else if keyPathComponents.count > 1, let arr = obj as? [Any] {
let tail = keyPathComponents.dropFirst()
return valueFor(tail, array: arr)
} else {
return (obj != nil, obj)
}
}
return (false, nil)
}
private func valueFor(_ keyPathComponents: ArraySlice<String>, array: [Any]) -> (Bool, Any?) {
guard !keyPathComponents.isEmpty else { return (false, nil) }
if let keyPath = keyPathComponents.first, let index = Int(keyPath), index >= 0 && index < array.count {
let obj = array[index]
if obj is NSNull {
return (true, nil)
} else if keyPathComponents.count > 1, let dict = obj as? [String: Any] {
let tail = keyPathComponents.dropFirst()
return valueFor(tail, dict: dict)
} else if keyPathComponents.count > 1, let arr = obj as? [Any] {
let tail = keyPathComponents.dropFirst()
return valueFor(tail, array: arr)
} else {
return (true, obj)
}
}
return (false, nil)
}
复制代码
其中在处理分隔符上,采用的是递归调用的方式,不过就咱们目前项目中,尚未用到过。
上述这几个步骤,就是ObjectMapper的核心方法。我也根据这些步骤,本身实现了一个解析的库。
可是这个只能解析一些最简单的类型,其余的像enum之类的,还须要作一些自定义的转化。主要的数据转化都在Operators文件夹中。
SwiftyJSON对外暴露的主要的构造器:
public init(data: Data, options opt: JSONSerialization.ReadingOptions = []) throws
public init(_ object: Any)
public init(parseJSON jsonString: String)
复制代码
最终调用的构造器为:
fileprivate init(jsonObject: Any)
复制代码
自定义了几个类型:
public enum Type: Int {
case number
case string
case bool
case array
case dictionary
case null
case unknown
}
复制代码
存储对象:
/// Private object
fileprivate var rawArray: [Any] = []
fileprivate var rawDictionary: [String: Any] = [:]
fileprivate var rawString: String = ""
fileprivate var rawNumber: NSNumber = 0
fileprivate var rawNull: NSNull = NSNull()
fileprivate var rawBool: Bool = false
/// JSON type, fileprivate setter
public fileprivate(set) var type: Type = .null
/// Error in JSON, fileprivate setter
public fileprivate(set) var error: SwiftyJSONError?
复制代码
解析过程
主要是在object属性的get & set方法中进行。而后将解析后的值存储到上述的属性中去。解析过程当中,有个unwrap方法值得咱们关注。
unwrap:
/// Private method to unwarp an object recursively
private func unwrap(_ object: Any) -> Any {
switch object {
case let json as JSON:
return unwrap(json.object)
case let array as [Any]:
return array.map(unwrap)
case let dictionary as [String: Any]:
var unwrappedDic = dictionary
for (k, v) in dictionary {
unwrappedDic[k] = unwrap(v)
}
return unwrappedDic
default:
return object
}
}
复制代码
这个方法根据object的类型,对其进行递归的解析。
为了统一Array和Dictionary的下标访问的类型,自定义了一个enum类型,JSONKey:
public enum JSONKey {
case index(Int)
case key(String)
}
// To mark both String and Int can be used in subscript.
extension Int: JSONSubscriptType {
public var jsonKey: JSONKey {
return JSONKey.index(self)
}
}
extension String: JSONSubscriptType {
public var jsonKey: JSONKey {
return JSONKey.key(self)
}
}
复制代码
而后就是喜闻乐见的subscript语法糖:
```
let json = JSON[data]
let path = [9,"list","person","name"]
let name = json[path]
```
public subscript(path: [JSONSubscriptType]) -> JSON
// let name = json[9]["list"]["person"]["name"]
public subscript(path: JSONSubscriptType...) -> JSON
复制代码
最后就是暴露字段,给开发者使用。好比:
public var int: Int?
public var intValue: Int
复制代码
每一个类型都有optional和unoptional。
首先咱们知道,在Swift4.0之前,JSON数据解析成Model是多么的繁琐。举个例子:
/// Swift 3.0
if let data = json.data(using: .utf8, allowLossyConversion: true),
let dict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
print("name: \(dict["name"])")
}
/// 咱们只能获取到json对应的dict,至于转换成model的话,仍是须要采用上面的方式,本质都是递归转化,即key与property的对应转化。
复制代码
那么,在swift4.0后,咱们能够怎么作呢。以下:
struct DJJSON<T: Decodable> {
fileprivate let data: Data
init(data: Data?) {
if let data = data {
self.data = data
} else {
self.data = Data()
}
}
init(jsonString: String) {
let data = jsonString.data(using: .utf8, allowLossyConversion: true)
self.init(data: data)
}
func decode() -> T? {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(T.self, from: data)
return result
} catch let error {
print("error: \(error)")
}
return nil
}
}
if let r = DJJSON<GrocerProduct>(jsonString: json).decode() {
print("result: \(r.ability?.mathematics)")
print("imageUrl: \(r.imageUrl)")
}
复制代码
咱们只要保证转化的model是遵照Codable协议的便可。至于Key跟Property的转化,苹果默认就帮我作了。那么有的朋友就要问了,那怎么自定义Key呢,苹果给咱们提供了一个enum叫CodingKeys, 咱们只要在这个里面作自定义就好了,默认的话就是key与property是对应的。如:
private enum CodingKeys: String, CodingKey {
case mathematics = "math"
case physics, chemistry
}
复制代码
那么问题又来了,有些字段是蛇形的,像什么image_url,有没有办法不本身作自定义就能搞定呢,诶,还真有,那就是在swift4.1中提供的这个convertFromSnakeCase。
// 完成image_url与imageUrl的转化
decoder.keyDecodingStrategy = .convertFromSnakeCase
复制代码
那么,这个是怎么实现的呢,咱们很好奇,由于感受本身也能够作这个转化啊,是否是easy game。咱们去看swift的源码:
fileprivate static func _convertFromSnakeCase(_ stringKey: String) -> String {
guard !stringKey.isEmpty else { return stringKey }
// Find the first non-underscore character
guard let firstNonUnderscore = stringKey.index(where: { $0 != "_" }) else {
// Reached the end without finding an _
return stringKey
}
// Find the last non-underscore character
var lastNonUnderscore = stringKey.index(before: stringKey.endIndex)
while lastNonUnderscore > firstNonUnderscore && stringKey[lastNonUnderscore] == "_" {
stringKey.formIndex(before: &lastNonUnderscore)
}
let keyRange = firstNonUnderscore...lastNonUnderscore
let leadingUnderscoreRange = stringKey.startIndex..<firstNonUnderscore
let trailingUnderscoreRange = stringKey.index(after: lastNonUnderscore)..<stringKey.endIndex
var components = stringKey[keyRange].split(separator: "_")
let joinedString : String
if components.count == 1 {
// No underscores in key, leave the word as is - maybe already camel cased
joinedString = String(stringKey[keyRange])
} else {
joinedString = ([components[0].lowercased()] + components[1...].map { $0.capitalized }).joined()
}
// Do a cheap isEmpty check before creating and appending potentially empty strings
let result : String
if (leadingUnderscoreRange.isEmpty && trailingUnderscoreRange.isEmpty) {
result = joinedString
} else if (!leadingUnderscoreRange.isEmpty && !trailingUnderscoreRange.isEmpty) {
// Both leading and trailing underscores
result = String(stringKey[leadingUnderscoreRange]) + joinedString + String(stringKey[trailingUnderscoreRange])
} else if (!leadingUnderscoreRange.isEmpty) {
// Just leading
result = String(stringKey[leadingUnderscoreRange]) + joinedString
} else {
// Just trailing
result = joinedString + String(stringKey[trailingUnderscoreRange])
}
return result
}
复制代码
真的写的特别精炼跟严谨好吧,学习一下这个。
到这里,就结束了,谢谢你们。