ObjectMapper實(shí)踐(一)

前言

在OC階段使用模型轉(zhuǎn)換的框架有很多,代表有:JSONModel、 YYModel、MJExtension。
OC的原理主要是通過runtime 獲取類的屬性,在運(yùn)行時(shí)獲取Model的字段名集合,遍歷該集合,拿Key去JSON中取值并完成賦值。而且Swift 的屬性默認(rèn)并不是動(dòng)態(tài)屬性,我們能在運(yùn)行時(shí)獲取一個(gè)Model實(shí)例的所有字段、字段值,但卻無法給它賦值。事實(shí)上,我們拿到的value是原值的一個(gè)只讀拷貝,即使獲取到這個(gè)拷貝的地址寫入新值,也是無效的。
OC的轉(zhuǎn)換方式雖然在OC中完全適用,但是缺點(diǎn)也很嚴(yán)重,一方面只能只能繼承 NSObject ,并不支持Struct;還有一個(gè)更嚴(yán)重的問題,optional 的屬性不能正確解析,反正坑還是挺多的。

所以如果是項(xiàng)目中有Swift的Model,就需要找到一個(gè)更好的轉(zhuǎn)換方式。

為了解決這些問題,很多處理JSON的開源庫應(yīng)運(yùn)而生。在Swift中,這些開源庫主要朝著兩個(gè)方向努力:

  1. 保持JSON語義,直接解析JSON,但通過封裝使調(diào)用方式更優(yōu)雅、更安全;
  2. 預(yù)定義Model類,將JSON反序列化為類實(shí)例,再使用這些實(shí)例。

先討論第一種方式,其實(shí)我在16年前用Swift的時(shí)候主要是用第一種方式,最初是原始的解析方式,茫茫多的guard,很傻的方法。 然后我就開始用大名鼎鼎的SwiftyJSON,它本質(zhì)上仍然是根據(jù)JSON結(jié)構(gòu)去取值,使用起來順手、清晰。但是他有一個(gè)根本性的問題,如果key拼寫錯(cuò)誤,或者其他的拼寫錯(cuò)誤就會(huì)很崩潰。

第二種方式應(yīng)該是最優(yōu)化的,最合理的方式。每一個(gè)Model都會(huì)通過一個(gè)Mappable協(xié)議來表明JSON字典映射關(guān)系,然后實(shí)現(xiàn)JSON和對(duì)象的轉(zhuǎn)換。當(dāng)然還有一個(gè)黑魔法 HandyJSON ,通過分析Swift數(shù)據(jù)結(jié)構(gòu)在內(nèi)存中的布局,自動(dòng)分析出映射關(guān)系,進(jìn)一步降低開發(fā)者使用的成本。
下面來介紹ObjectMapper 的用法,實(shí)現(xiàn)思路,以及源碼分析。

ObjectMapper 介紹

ObjectMapper 是一個(gè)使用 Swift 編寫的用于 model 對(duì)象(類和結(jié)構(gòu)體)和 JSON 之間轉(zhuǎn)換的框架。

ObjectMapper特性
  • 將JSON映射到對(duì)象
  • 將對(duì)象映射到JSON
  • 嵌套對(duì)象(獨(dú)立,在數(shù)組或字典中)
  • 映射期間的自定義轉(zhuǎn)換
  • 結(jié)構(gòu)支持
  • 不可改變的支持
ObjectMapper可以映射由以下類型組成的類:
  • Int
  • Bool
  • Double
  • Float
  • String
  • RawRepresentable (Enums)
  • Array<Any>
  • Dictionary<String, Any>
  • Object<T: Mappable>
  • Array<T: Mappable>
  • Array<Array<T: Mappable>>
  • Set<T: Mappable>
  • Dictionary<String, T: Mappable>
  • Dictionary<String, Array<T: Mappable>>
  • Optionals of all the above
  • Implicitly Unwrapped Optionals of the above
基本用法

ObjectMapper中定義了一個(gè)協(xié)議Mappable

Mappable協(xié)議中聲明了兩個(gè)方法

mutation func mapping(map: Map)

init?(map: Map)

ObjectMapper使用 <-運(yùn)算符來定義每個(gè)成員變量如何映射到JSON和從JSON映射。

class User: Mappable {
    var username: String?
    var age: Int?
    var weight: Double!
    var array: [Any]?
    var dictionary: [String : Any] = [:]
    var bestFriend: User?                       // Nested User object
    var friends: [User]?                        // Array of Users
    var birthday: Date?

    required init?(map: Map) {

    }

    // Mappable
    func mapping(map: Map) {
        username    <- map["username"]
        age         <- map["age"]
        weight      <- map["weight"]
        array       <- map["arr"]
        dictionary  <- map["dict"]
        bestFriend  <- map["best_friend"]
        friends     <- map["friends"]
        birthday    <- (map["birthday"], DateTransform())
    }
}

struct Temperature: Mappable {
    var celsius: Double?
    var fahrenheit: Double?

    init?(map: Map) {

    }

    mutating func mapping(map: Map) {
        celsius     <- map["celsius"]
        fahrenheit  <- map["fahrenheit"]
    }
}

如果我們的類或結(jié)構(gòu)體如上面的示例一樣實(shí)現(xiàn)了協(xié)議,我們就可以方便的進(jìn)行JSON和模型之間的轉(zhuǎn)換

let JSONString = "{\"weight\": 180}"
let user = User(JSONString: JSONString)
user?.age = 10
user?.username = "ash"
user?.birthday = Date()
user?.weight = 180

if let jsonStr = user?.toJSONString(prettyPrint: true) {
        debugPrint(jsonStr)
}

當(dāng)然也可以通過Mapper類來進(jìn)行轉(zhuǎn)換

let user = Mapper<User>().map(JSONString: JSONString)

let JSONString = Mapper().toJSONString(user, prettyPrint: true)
嵌套對(duì)象的映射

正如前面所列,ObjectMapper支持嵌套對(duì)象的映射

{
    "distance" : {
        "text" : "102",
        "value" : 31
    }
}

我們想要直接取出distance對(duì)象中的value值,可以設(shè)置如下mapping

func mapping(map: Map) {
    distance <- map["distance.value"]
}
自定義轉(zhuǎn)換規(guī)則

ObjectMapper允許開發(fā)者在數(shù)據(jù)映射過程中指定轉(zhuǎn)換規(guī)則

class People: Mappable {
   var birthday: NSDate?
   
   required init?(_ map: Map) {
       
   }
   
   func mapping(map: Map) {
       birthday <- (map["birthday"], DateTransform())
   }
   
   let JSON = "\"birthday\":1458117795332"
   let result = Mapper<People>().map(JSON)
}

由于我們指定了birthday的轉(zhuǎn)換規(guī)則,所以上述代碼在解析JSON數(shù)據(jù)的時(shí)候會(huì)將long類型轉(zhuǎn)換成Date類型

除了使用ObjectMapper給我們提供的轉(zhuǎn)換規(guī)則外,我們還可以通過實(shí)現(xiàn)TransformType協(xié)議來自定義我們的轉(zhuǎn)換規(guī)則
ObjectMapper為我們提供了一個(gè)TransformOf類來實(shí)現(xiàn)轉(zhuǎn)換結(jié)果,TransformOf實(shí)際就是實(shí)現(xiàn)了TransformType協(xié)議的,TransformOf有兩個(gè)類型的參數(shù)和兩個(gè)閉包參數(shù),類型表示參與轉(zhuǎn)換的數(shù)據(jù)的類型,閉包表示轉(zhuǎn)換的規(guī)則

public protocol TransformType {
    typealias Object
    typealias JSON
    
    func transformFromJSON(value: AnyObject?) -> Object?
    func transformToJSON(value: Object?) -> JSON?
}

let transform = TransformOf<Int, String>(fromJSON: { (value: String?) -> Int? in 
}, toJSON: { (value: Int?) -> String? in 
  // transform value from Int? to String?
  if let value = value {
      return String(value)
  }
  return nil
})

func mapping(map: Map) {
  id <- (map["id"], transform)
}
泛型對(duì)象

ObjectMapper同樣可以處理泛型類型的參數(shù),不過這個(gè)泛型類型需要在實(shí)現(xiàn)了Mappable協(xié)議的基礎(chǔ)上才可以正常使用

class User: Mappable {
    var name: String?
    
    required init?(_ map: Map) {
        
    }
    
    func mapping(_ map: Map) {
        name <- map["name"]
    }
}

class Result<T: Mappable>: Mappable {
    var result: T?
    
    required init?(_ map: Map) {
        
    }
    
    func mapping(map: Map) {
        result <- map["result"]
    }
}

let JSON = "{\"result\": {\"name\": \"anenn\"}}"
let result = Mapper<Result<User>>().map(JSON)

基本上的大部分常用用法都介紹完了,滿足日常的開發(fā)需求應(yīng)該是沒問題的,下面我們要研究一下源碼部分

源碼解析

功能分類

根據(jù)實(shí)現(xiàn)的思路來分類應(yīng)該可以分成三類:

  1. Core 部分
  2. Operators 部分
  3. Transforms 部分

其實(shí) coreOperators 也可以歸為一類,但是拆開來看更加容易理解,還是拆開來吧。
因?yàn)樵创a比較多,這篇文章先介紹 Core 部分,了解這部分基本上的實(shí)現(xiàn)思路就已經(jīng)很明確了,然后在最后會(huì)介紹一下 Sourcery 的自動(dòng)代碼生成,不然 mapping 方法中的代碼寫的讓人很絕望。

Mappable

Mappable相關(guān)的協(xié)議有StaticMappableImmutableMappable,我們先將 StaticMappableImmutableMappable 這兩種協(xié)議的處理邏輯放一放,直接關(guān)注最重要的 Mappable 協(xié)議的實(shí)現(xiàn),了解了 Mappable 另外兩個(gè)很好理解。

/// BaseMappable should not be implemented directly. Mappable or StaticMappable should be used instead
public protocol BaseMappable {
    /// This function is where all variable mappings should occur. It is executed by Mapper during the mapping (serialization and deserialization) process.
    mutating func mapping(map: Map)
}

public protocol Mappable: BaseMappable {
    /// This function can be used to validate JSON prior to mapping. Return nil to cancel mapping at this point
    init?(map: Map)
}

public extension BaseMappable {
    /// Initializes object from a JSON String
    public init?(JSONString: String, context: MapContext? = nil) {
        if let obj: Self = Mapper(context: context).map(JSONString: JSONString) {
            self = obj
        } else {
            return nil
        }
    }
    
    /// Initializes object from a JSON Dictionary
    public init?(JSON: [String: Any], context: MapContext? = nil) {
        if let obj: Self = Mapper(context: context).map(JSON: JSON) {
            self = obj
        } else {
            return nil
        }
    }
    
    /// Returns the JSON Dictionary for the object
    public func toJSON() -> [String: Any] {
        return Mapper().toJSON(self)
    }
    
    /// Returns the JSON String for the object
    public func toJSONString(prettyPrint: Bool = false) -> String? {
        return Mapper().toJSONString(self, prettyPrint: prettyPrint)
    }
}

BaseMappable為實(shí)現(xiàn) MappableModel 提供了四種實(shí)例方法,有兩個(gè)是初始化方法,當(dāng)然你也可以自己新建一個(gè) Mapper 來初始化;還有兩個(gè)是 Model 轉(zhuǎn) JSON 的方法。

Mapper

繼續(xù)看 Mapper 的代碼,Mapper中核心代碼為下面的方法

    /// Maps a JSON dictionary to an object that conforms to Mappable
    public func map(JSON: [String: Any]) -> N? {
        let map = Map(mappingType: .fromJSON, JSON: JSON, context: context, shouldIncludeNilValues: shouldIncludeNilValues)
        
        if let klass = N.self as? StaticMappable.Type { // Check if object is StaticMappable
            if var object = klass.objectForMapping(map: map) as? N {
                object.mapping(map: map)
                return object
            }
        } else if let klass = N.self as? Mappable.Type { // Check if object is Mappable
            if var object = klass.init(map: map) as? N {
                object.mapping(map: map)
                return object
            }
        } else if let klass = N.self as? ImmutableMappable.Type { // Check if object is ImmutableMappable
            do {
                return try klass.init(map: map) as? N
            } catch let error {
                #if DEBUG
                let exception: NSException
                if let mapError = error as? MapError {
                    exception = NSException(name: .init(rawValue: "MapError"), reason: mapError.description, userInfo: nil)
                } else {
                    exception = NSException(name: .init(rawValue: "ImmutableMappableError"), reason: error.localizedDescription, userInfo: nil)
                }
                exception.raise()
                #endif
            }
        } else {
            // Ensure BaseMappable is not implemented directly
            assert(false, "BaseMappable should not be implemented directly. Please implement Mappable, StaticMappable or ImmutableMappable")
        }
        
        return nil
    }

根據(jù)N的協(xié)議類型走不同的協(xié)議方法,最終得到 object。
讓我們用 Mappable 來舉例,先回到之前協(xié)議中的方法

mutation func mapping(map: Map)

init?(map: Map)

這樣對(duì)著看就很好理解了,init?(map: Map) 沒有 return nil 的時(shí)候,就會(huì)調(diào)用 func mapping(map: Map) 方法來指定映射關(guān)系,那這個(gè)映射關(guān)系有什么作用呢,后面會(huì)慢慢介紹。

extension Mapper {
    // MARK: Functions that create JSON from objects    
    
    ///Maps an object that conforms to Mappable to a JSON dictionary <String, Any>
    public func toJSON(_ object: N) -> [String: Any] {
        var mutableObject = object
        let map = Map(mappingType: .toJSON, JSON: [:], context: context, shouldIncludeNilValues: shouldIncludeNilValues)
        mutableObject.mapping(map: map)
        return map.JSON
    }
    
    ///Maps an array of Objects to an array of JSON dictionaries [[String: Any]]
    public func toJSONArray(_ array: [N]) -> [[String: Any]] {
        return array.map {
            // convert every element in array to JSON dictionary equivalent
            self.toJSON($0)
        }
    }
    
    ///Maps a dictionary of Objects that conform to Mappable to a JSON dictionary of dictionaries.
    public func toJSONDictionary(_ dictionary: [String: N]) -> [String: [String: Any]] {
        return dictionary.map { (arg: (key: String, value: N)) in
            // convert every value in dictionary to its JSON dictionary equivalent
            return (arg.key, self.toJSON(arg.value))
        }
    }
    
    ///Maps a dictionary of Objects that conform to Mappable to a JSON dictionary of dictionaries.
    public func toJSONDictionaryOfArrays(_ dictionary: [String: [N]]) -> [String: [[String: Any]]] {
        return dictionary.map { (arg: (key: String, value: [N])) in
            // convert every value (array) in dictionary to its JSON dictionary equivalent
            return (arg.key, self.toJSONArray(arg.value))
        }
    }
    
    /// Maps an Object to a JSON string with option of pretty formatting
    public func toJSONString(_ object: N, prettyPrint: Bool = false) -> String? {
        let JSONDict = toJSON(object)
        
        return Mapper.toJSONString(JSONDict as Any, prettyPrint: prettyPrint)
    }

    /// Maps an array of Objects to a JSON string with option of pretty formatting  
    public func toJSONString(_ array: [N], prettyPrint: Bool = false) -> String? {
        let JSONDict = toJSONArray(array)
        
        return Mapper.toJSONString(JSONDict as Any, prettyPrint: prettyPrint)
    }
    
    /// Converts an Object to a JSON string with option of pretty formatting
    public static func toJSONString(_ JSONObject: Any, prettyPrint: Bool) -> String? {
        let options: JSONSerialization.WritingOptions = prettyPrint ? .prettyPrinted : []
        if let JSON = Mapper.toJSONData(JSONObject, options: options) {
            return String(data: JSON, encoding: String.Encoding.utf8)
        }
        
        return nil
    }
    
    /// Converts an Object to JSON data with options
    public static func toJSONData(_ JSONObject: Any, options: JSONSerialization.WritingOptions) -> Data? {
        if JSONSerialization.isValidJSONObject(JSONObject) {
            let JSONData: Data?
            do {
                JSONData = try JSONSerialization.data(withJSONObject: JSONObject, options: options)
            } catch let error {
                print(error)
                JSONData = nil
            }
            
            return JSONData
        }
        
        return nil
    }
}

Mapper 還有一些 toJSON 的方法,這邊的方法也很好理解,具體的實(shí)現(xiàn)都是在 Map 的一些方法,要知道這些方法具體實(shí)現(xiàn)就需要繼續(xù)往下看。

Map

Map 中有兩個(gè)核心的方法,先看自定義下標(biāo)的方法,分析一下最重要的那個(gè)自定義下標(biāo)的方法

/// Sets the current mapper value and key.
/// The Key paramater can be a period separated string (ex. "distance.value") to access sub objects.
public subscript(key: String) -> Map {
    // save key and value associated to it
    return self.subscript(key: key)
}

public subscript(key: String, delimiter delimiter: String) -> Map {
    return self.subscript(key: key, delimiter: delimiter)
}

public subscript(key: String, nested nested: Bool) -> Map {
    return self.subscript(key: key, nested: nested)
}

public subscript(key: String, nested nested: Bool, delimiter delimiter: String) -> Map {
    return self.subscript(key: key, nested: nested, delimiter: delimiter)
}

public subscript(key: String, ignoreNil ignoreNil: Bool) -> Map {
    return self.subscript(key: key, ignoreNil: ignoreNil)
}

public subscript(key: String, delimiter delimiter: String, ignoreNil ignoreNil: Bool) -> Map {
    return self.subscript(key: key, delimiter: delimiter, ignoreNil: ignoreNil)
}

public subscript(key: String, nested nested: Bool, ignoreNil ignoreNil: Bool) -> Map {
    return self.subscript(key: key, nested: nested, ignoreNil: ignoreNil)
}

public subscript(key: String, nested nested: Bool?, delimiter delimiter: String, ignoreNil ignoreNil: Bool) -> Map {
    return self.subscript(key: key, nested: nested, delimiter: delimiter, ignoreNil: ignoreNil)
}

private func `subscript`(key: String, nested: Bool? = nil, delimiter: String = ".", ignoreNil: Bool = false) -> Map {
    // save key and value associated to it
    currentKey = key
    keyIsNested = nested ?? key.contains(delimiter)
    nestedKeyDelimiter = delimiter
    
    if mappingType == .fromJSON {
        // check if a value exists for the current key
        // do this pre-check for performance reasons
        if keyIsNested {
            // break down the components of the key that are separated by delimiter
            (isKeyPresent, currentValue) = valueFor(ArraySlice(key.components(separatedBy: delimiter)), dictionary: JSON)
        } else {
            let object = JSON[key]
            let isNSNull = object is NSNull
            isKeyPresent = isNSNull ? true : object != nil
            currentValue = isNSNull ? nil : object
        }
        
        // update isKeyPresent if ignoreNil is true
        if ignoreNil && currentValue == nil {
            isKeyPresent = false
        }
    }
    return self
}

另一個(gè)核心的方法就是通過自定義下標(biāo)的值,從JSON字典中根據(jù)key獲取了value。

/// Fetch value from JSON dictionary, loop through keyPathComponents until we reach the desired object
private func valueFor(_ keyPathComponents: ArraySlice<String>, dictionary: [String: Any]) -> (Bool, Any?) {
    // Implement it as a tail recursive function.
    if keyPathComponents.isEmpty {
        return (false, nil)
    }
    
    if let keyPath = keyPathComponents.first {
        let isTail = keyPathComponents.count == 1
        let object = dictionary[keyPath]
        if object is NSNull {
            return (isTail, nil)
        } else if keyPathComponents.count > 1, let dict = object as? [String: Any] {
            let tail = keyPathComponents.dropFirst()
            return valueFor(tail, dictionary: dict)
        } else if keyPathComponents.count > 1, let array = object as? [Any] {
            let tail = keyPathComponents.dropFirst()
            return valueFor(tail, array: array)
        } else {
            return (isTail && object != nil, object)
        }
    }
    
    return (false, nil)
}

/// Fetch value from JSON Array, loop through keyPathComponents them until we reach the desired object
private func valueFor(_ keyPathComponents: ArraySlice<String>, array: [Any]) -> (Bool, Any?) {
    // Implement it as a tail recursive function.
    
    if keyPathComponents.isEmpty {
        return (false, nil)
    }
    
    //Try to convert keypath to Int as index
    if let keyPath = keyPathComponents.first,
        let index = Int(keyPath) , index >= 0 && index < array.count {
        
        let isTail = keyPathComponents.count == 1
        let object = array[index]
        
        if object is NSNull {
            return (isTail, nil)
        } else if keyPathComponents.count > 1, let array = object as? [Any]  {
            let tail = keyPathComponents.dropFirst()
            return valueFor(tail, array: array)
        } else if  keyPathComponents.count > 1, let dict = object as? [String: Any] {
            let tail = keyPathComponents.dropFirst()
            return valueFor(tail, dictionary: dict)
        } else {
            return (isTail, object)
        }
    }
    
    return (false, nil)
}

看到這里其實(shí) Core 部分的代碼基本上就看完了,還有一些toJSON的方法,其他的類同的方法,那些對(duì)于理解 ObjectMapper 沒有影響。

寫在最后

Sourcery

簡單介紹一些 Sourcery 這個(gè)自動(dòng)生成代碼的工具。
Sourcery 是一個(gè) Swift 代碼生成的開源命令行工具,它 (通過 SourceKitten 使用 Apple 的 SourceKit 框架,來分析你的源碼中的各種聲明和標(biāo)注,然后套用你預(yù)先定義的 Stencil 模板 (一種語法和 Mustache 很相似的 Swift 模板語言) 進(jìn)行代碼生成。我們下面會(huì)先看一個(gè)使用 SourceKitten 最簡單的例子,來說明如何使用這個(gè)工具。然后再針對(duì)我們的字典轉(zhuǎn)換問題進(jìn)行實(shí)現(xiàn)。

安裝 SourceKitten 非常簡單,brew install sourcery 即可。不過,如果你想要在實(shí)際項(xiàng)目中使用這個(gè)工具的話,我建議直接從發(fā)布頁面下載二進(jìn)制文件,放到 Xcode 項(xiàng)目目錄中,然后添加 Run Script 的 Build Phase 來在每次編譯的時(shí)候自動(dòng)生成。

之前說過了 mapping 函數(shù)實(shí)現(xiàn)起來過于臃腫耗時(shí),你可以用插件來生成 mapping 函數(shù)
用于生成MappableImmutableMappable代碼的Xcode插件
但是Xcode 8之后不讓用插件了,除非用野路子重簽名的方式安裝插件,而且安裝了還不一定能用,反正那個(gè)很坑,還要復(fù)制一個(gè)Xcode用來打包上傳,本弱雞電腦根本沒那么多空間。
兩個(gè)方法我都試過了, 個(gè)人覺得 SourceKitten 更加適合,那個(gè)插件的確實(shí)不好用,還有一種方式,可以在網(wǎng)站上自動(dòng)生成,然后復(fù)制進(jìn)來。
接下來就可以嘗試以下書寫模板代碼了??梢詤⒄?Sourcery 文檔 關(guān)于單個(gè) TypeVariable 的部分的內(nèi)容來實(shí)現(xiàn)。另外,可以考慮使用 --watch 模式來在文件改變時(shí)自動(dòng)生成代碼,來實(shí)時(shí)觀察結(jié)果。

如果聲明一個(gè)struct

protocol AutoMappable {}

struct Person {
    
    var firstName: String
    var lastName: String
    var birthDate: Date
    var friend: [String]
    var lalala: Dictionary<String, Any>
    var age: Int {
        return Calendar.current.dateComponents([.year],
                                               from: birthDate,
                                               to: Date()).year ?? -1
    }
}
extension Person: AutoMappable {}

下面是我的模版代碼

import ObjectMapper

{% for type in types.implementing.AutoMappable|struct %}
// MARK: {{ type.name }} Mappable
extension {{type.name}}: Mappable {

    init?(map: Map) {
        return nil
    }
    
    mutating func mapping(map: Map) {
    {% for variable in type.storedVariables %} 
        {% if variable.isArray %}
            {{variable.name}} <- map["{{variable.name}}.0.value"]
        {% elif variable.isDictionary %}
            {{variable.name}} <- map["{{variable.name}}.value"]
        {% else %}
            {{variable.name}} <- map["{{variable.name}}"]
        {% endif %}
    {% endfor %}
    }
}
{% endfor %}

自動(dòng)生成的代碼顯示如下:

import ObjectMapper

// MARK: Person Mappable
extension Person: Mappable {

    init?(map: Map) {
        return nil
    }
    mutating func mapping(map: Map) {
            firstName <- map["firstName"]
            lastName  <- map["lastName"]
            birthDate <- map["birthDate"]
            friend    <- map["friend.0.value"]
            lalala    <- map["lalala.value"]
    }
}

上面的這種方式顯然是運(yùn)行時(shí)最高效的方式,所以強(qiáng)烈推薦是這個(gè)方法來使用ObjectMapper。
后面會(huì)繼續(xù)介紹 ObjectMapper 其他源碼的實(shí)現(xiàn)思路。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明AI閱讀 16,192評(píng)論 3 119
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,172評(píng)論 4 61
  • 在我故鄉(xiāng)里,春夏季最常見的也就是那黃黃的油菜花,高低起伏的群山環(huán)繞著我的小鎮(zhèn),那山上布滿了密密麻麻的油菜花,...
    2020級(jí)1班閱讀 584評(píng)論 7 0
  • 2014年1月1日,長江公司為其200名管理人員每人授予100份現(xiàn)金股票增值權(quán),這些人員從2014年1月1日起必須...
    shelter2033閱讀 326評(píng)論 0 0

友情鏈接更多精彩內(nèi)容