代码基于Swift4.0.
导读,例子中用到的book.json
文件
{
"title": "War and Peace: A protocol oriented approach to diplomacy",
"author": "A. Keed Decoder",
"rating": null,
"publishedAt": "019-04-16T9:24:37TPM.000Z"
}
使用差异
一、 是否需要映射字段
原生
- 属性名和
json
返回的字段名完全一样则无需添加映射 - 属性名和
json
返回的字段名只要有一个不同,则需要用enum CodingKey
全部添加映射
Mapper
- 即使属性名和json返回的字段名完全一样,也必须用
func mapping(map: Map)
添加映射。 否则可选的属性为nil。
import UIKit
import ObjectMapper
struct XXBook: Mappable {
// 通过Mapper转模型
var title: String?
var author: String?
var rating: Float?
var publishedAt: String?
init?(map: Map) {
}
mutating func mapping(map: Map) {
title <- map["title"]
author <- map["author"]
rating <- map["rating"]
publishedAt <- map["publishedAt"]
}
}
struct XXXBook: Codable {
// 通过原生转模型
var title: String?
var author: String?
var rating: Float?
var publishedAt: String?
}
二、 转模型
原生
- 通过
JSONDecoder
Data
转
//用原生方式转模型
guard let path = Bundle.main.path(forResource: "book", ofType: "json") else {
return
}
guard let jsonData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) else {
return
}
let decoder = JSONDecoder()
if let book = try? decoder.decode(XXXBook.self, from: jsonData) {
print(book)
} else {
print("decode failed")
}
Mapper
- 通过
JSONSerialization
转
guard let path = Bundle.main.path(forResource: "book", ofType: "json") else {
return
}
guard let jsonData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) else {
return
}
guard let jsonDict = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) else {
return
}
guard let json = jsonDict as? [String: Any] else {
return
}
// 用ObjectMapper转模型
if let book = XXBook(JSON: json) {
print(book)
} else {
print("转模型 failed")
}
三、 解析字段
原生
- 解析
json
中返回带null
的数据,如导读的rating
所示,属性为nil
XXBook(title: Optional("War and Peace: A protocol oriented approach to diplomacy"), author: Optional("A. Keed Decoder"), rating: nil, publishedAt: Optional("019-04-16T9:24:37TPM.000Z"))
- 解析
json
中返回数据类型不一致,如将rating
改为String
类型返回"rating": "5.0"
,属性为nil
XXBook(title: Optional("War and Peace: A protocol oriented approach to diplomacy"), author: Optional("A. Keed Decoder"), rating: nil, publishedAt: Optional("019-04-16T9:24:37TPM.000Z"))
Mapper
- 解析
json中
返回带null
的数据,如导读的rating
所示,属性为nil
(和原生结果一样)
XXXBook(title: Optional("War and Peace: A protocol oriented approach to diplomacy"), author: Optional("A. Keed Decoder"), rating: nil, publishedAt: Optional("019-04-16T9:24:37TPM.000Z"))
- 解析json中返回数据类型不一致,如将rating 改为string类型返回
"rating": "5.0"
,整个Book
对象解析失败decode failed
decode failed
解析特殊类型差异
一、 日期类型
首先我们需要改造book.json
, 和XXBook
, XXXBook
类型
book.json
填入正确的时间格式
{
"title": "War and Peace: A protocol oriented approach to diplomacy",
"author": "A. Keed Decoder",
"rating": 5.0,
"publishedAt": "2018-01-01T00:00:00.000Z"
}
XXBook
, XXXBook
。publishedAt
字段改为Date
struct XXBook: Mappable {
// 通过Mapper转模型
var title: String?
var author: String?
var rating: Float?
var publishedAt: Date?
init?(map: Map) {
}
mutating func mapping(map: Map) {
title <- map["title"]
author <- map["author"]
rating <- map["rating"]
publishedAt <- (map["publishedAt"], ObjectMapperDateTransform()) //mapper转换日期格式 需要自定义变换类需要实现 TransformType 协议。
}
}
struct ObjectMapperDateTransform: TransformType {
public typealias Object = Date
public typealias JSON = String
public init() {}
public func transformFromJSON(_ value: Any?) -> Date? {
if let timeStr = value as? String {
return XXDataConvert.stringConvertDate(string: timeStr)
}
return nil
}
public func transformToJSON(_ value: Date?) -> String? {
if let date = value {
return XXDataConvert.dateConvertString(date: date)
}
return nil
}
}
struct XXXBook: Codable {
// 通过原生转模型
var title: String?
var author: String?
var rating: Float?
var publishedAt: Date?
}
struct XXDataConvert {
// 为Mapper作日期转换时抽了几个方法
static func dateFormatter() -> DateFormatter {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
return dateFormatter
}
static func dateConvertString(date: Date) -> String? {
let formatter = dateFormatter()
let date = formatter.string(from: date)
return date.components(separatedBy: " ").first
}
static func stringConvertDate(string: String) -> Date? {
let dateFormat = dateFormatter()
return dateFormat.date(from: string)
}
}
原生
- 解析
Date
只需要为decoder
加一句代码
decoder.dateDecodingStrategy = .formatted(XXDataConvert.dateFormatter())
Mapper
- 解析
Date
,需要自定义一个遵守TransformType
协议的变换结构体(/类),并且实现TransformType
协议中必须要实现的方法。
public protocol TransformType {
associatedtype Object
associatedtype JSON
func transformFromJSON(_ value: Any?) -> Object?
func transformToJSON(_ value: Object?) -> JSON?
}
二、 枚举类型
在XXBook
, XXXBook
中新增字段 authorGender
。 类型是 enum XXGender
enum XXGender: String, Codable {
case male
case female
case other
}
原生
- 解析
authorGender
需要写原值类型并遵循Codable
协议。 如果解析字段和返回的json
字段一致。解析时则什么都不用做了
var authorGender: XXGender?
Mapper
- 解析
authorGender
需在mapping
方法中加上
authorGender <- (map["authorGender"], EnumTransform())
总结
- 在使用原生解析非常方便,字段一致不用写字段映射,对Date,Enum的兼容也很好。只是如果后台返回的字段类型和解析时属性的类型不一致时会导致整个对象解析失败。代价很大。
- Mapper 必须为每个字段映射即使和后台返回的字段名完全一样,添加特殊类型的解析略微复杂。但是会后台返回的数据兼容较好。
在项目中还是结合自己的需求做选择。 像本人负责的项目中,同事无法接受如果后台返回的类型不一致而使对象解析失败,所以坚决选择用ObjectMapper了。