Mappable 是一个轻量级、灵活、易于使用的框架,用于将 JSON 转换为模型,特别针对不可变属性的初始化进行了优化。
struct Flight: Mappable {
let number: String
let time: Date
init(map: Mapper) throws {
// with the help of @dynamicMemberLookup feature
number = try map.id()
time = try map.time()
// or use the old way
// number = try map.from("id")
// time = try map.from("time")
}
}
// Flight(JSONString: json)
同时提供了一个 Xcode 插件,用于自动生成实现代码。
大多数 JSON 转换为模型的库不能很好地处理不可变属性的初始化。它们需要使用 `var` 和可空类型来声明属性,这违反了 Swift 的精神并导致代码质量下降。 Mappable 诞生就是为了解决这个问题。
优点 | 缺点 | |
---|---|---|
Codable | - Swift 原生支持 - 自动转换(无需指定映射关系) - 支持双向转换 |
- 不够灵活 - 不支持继承的类 |
HandyJSON | - 自动转换(无需指定映射关系) - 双向转换 |
不支持不可变属性 |
ObjectMapper | 双向转换 | - 对不可变属性的支持较弱* - 多种模式导致混乱 - 缺少对某些类型组合的支持。 |
SwiftyJSON | 不是一个 JSON 对象转换器。 它只是一个处理 JSON 数据的便捷工具。 |
* 1) 不能方便地处理可选类型。 2) 不支持兼容的类型转换,这会导致 JSON 中哪怕是很小的格式错误都会导致整个对象转换失败。
Mappable 很大程度上受到了 ObjectMapper 的启发。您可以将 Mappable 视为 ObjectMapper 中 `ImmutableMappable` 的改进版本。
为了支持映射,一个类型应该实现 `Mappable` 协议,该协议只有一个初始化方法
class Country: Mappable {
let name: String
let cities: [City] // struct City: Mappable { ... }
let atContinent: Continent // enum Continent: Mappable { ... }
required init(map: Mapper) throws {
name = try map.from("name")
cities = try map.from("city")
atContinent = try map.from("location.continent")
}
}
您只需要编写映射关系:属性的键路径。虽然这些行只是普通的赋值语句,但不需要指定类型,因此您可以将这些行视为映射关系的特殊表示形式。(您可以将该行理解为“尝试从 XXX 映射(值))😆 )
然后你可以这样初始化一个对象
// NOTE: these initializer throw errors, you should do error handling
let c = try Country(JSON: jsonDict)
let d = try? Country(JSONString: jsonString)
// just use `??`
cities = try map.from("city") ?? []
即使 JSON 中没有对应的数据或数据格式错误,`Optional` 类型也不会抛出错误。在这种情况下,将分配一个 `nil` 值。
如果您将属性声明为可选类型,可能意味着 JSON 中并非严格要求此数据。因此,您希望在实际没有数据时获得一个 nil 值。
struct User: Mappable {
let ID: String
let summary: String?
init(map: Mapper) throws {
ID = try map.from("id")
summary = try map.from("summary")
}
}
let json = ["id": "a123"]
let user = try! User(JSONObject: json) // It won't crash.
从以下类型转换 | |
---|---|
Int, Double, Float, CGFloat | String |
Bool | Int, "true", "True", "TRUE", "YES", "false", "False", "FALSE", "NO", "0", "1" |
String | Int, NSNumber |
URL | String |
Date | Double(secondsSince1970), String (RFC 3339, e.g. 2016-06-13T16:00:00+00:00 ) |
更多详情请参见此处。
初始化器中的内容只是简单的赋值,因此您可以对数据进行任何操作。使用 `map.getRootValue()` 和 `map.getValue(keyPath:)` 获取原始 JSON 值并执行您想要的操作。
为了方便日期转换,`Mapper` 中还有一个 `options` 属性可以设置自定义的日期策略。(更复杂的例子请参见此处)
符合 `RawRepresentable` 的枚举具有 `Mappable` 的默认实现。您只需将 `Mappable` 的符合性声明为您的枚举类型,它就可以工作了。
对于具有关联值的枚举,您可以手动实现
enum EnumWithValues: Mappable {
case a(Int)
case b(String)
init(map: Mapper) throws {
// It could be initialized with a json date like:
// {"type": "a", value: 123}
let value = try map.getValue("type", as: String.self)
switch value {
case "a":
self = .a(try map.from("value"))
case "b":
self = .b(try map.from("value"))
default:
throw ErrorType.JSONStructureNotMatchDesired(value, "string a/b")
}
}
}
class ChildModel: BaseModel {
let b : Int
required init(map: Mapper) throws {
b = try map.from("b")
try super.init(map: map)
}
}
使用键路径 "AAA.BBB" 来映射 JSON 中的多级路径值
// let json = """ {"AAA": {"BBB": 1}} """
b = try map.from("AAA.BBB")
// use `n` to get a n-th value in array
// let json = """ {"AAA": [11,22,33]} """
b = try map.from("AAA.`2`") // b = 33
如果一个普通键自然包含 `.`,你可以使用像 `map.from("a.file", keyPathIsNested: false)` 这样的方法,将该键视为单级路径。
pod 'Mappable'
.Package(url: "https://github.com/leavez/Mappable.git", from: "1.5.0"),
github "leavez/Mappable"
Mappable 在 MIT 许可下可用。有关更多信息,请参见 LICENSE 文件。