在开发移动应用程序时,我们面临着将服务器数据解析为内部应用程序模型的需求。在大多数情况下,此数据采用JSON格式。从Swift 4开始,解析JSON的主要工具是使用协议Decodable
和对象JSONDecoder
。
这种方法大大简化了解析数据的过程,并减少了样板代码的数量。在大多数情况下,创建具有名称属性的模型非常简单,就像JSON对象中的字段JSONDecoder
一样,其余的工作将由您完成。最少的代码,最大的收益。但是,这种方法有一个缺点,即解析器的忠诚度极低。我会解释。如果内部数据模型(可解码对象)与JSON中的数据之间存在差异,JSONDecoder
抛出错误,我们将丢失整个对象。也许在某些情况下,这种行为模型是可取的,特别是在涉及金融交易时。但是,在许多情况下,使解析过程更加忠诚将很有用。在本文中,我想分享我的经验,并讨论提高这种忠诚度的主要方法。
过滤无效对象
好吧,第一项当然是过滤无效对象。在许多情况下,如果嵌套对象之一无效,我们就不想丢失整个对象。这适用于单个对象和对象数组。我举一个例子。假设我们提出了一个销售商品的应用程序,并且在其中一个屏幕上我们以这种形式获得了一份商品清单。
{
"products": [
{...},
{...},
....
]
}
如果其中一项产品未通过验证,我们也不想丢失整个产品列表。在这里,最好过滤该对象并将列表的其余部分返回给用户。当然,承诺并解决这个问题并不是多余的,但是显示空白纸仍然不是最佳解决方案。不幸的是,它JSONDecoder
没有实现“开箱即用”的数组对象过滤,并且需要编写其他代码。
此外,每个产品均由以下对象表示:
{
"id": 1,
"title": "Awesome product",
"price": 12.2,
"image": {
"id": 1,
"url": "http://image.png",
"thumbnail_url": "http://thumbnail.png"
}
}
如您所见,其中有一个嵌套对象image
。问题就出现了:我们是要过滤包含无效图像对象的产品,还是要通过设置属性来使其离开image = nil
?不幸的是,与数据数组一样,它JSONDecoder
不允许过滤出单个属性。
, JSONDecoder
2 : decode
decodeIfPresent
. optional
, nil
, , null
. .
, , . , init(decoder)
try?
nil
. , boilerplate . .
struct FailableDecodable<Value: Decodable>: Decodable {
var wrappedValue: Value?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try? container.decode(Value.self)
}
}
struct FailableDecodableArray<Value: Decodable>: Decodable {
var wrappedValue: [Value]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements: [Value] = []
while !container.isAtEnd {
if let element = try? container.decode(Value.self) {
elements.append(element)
}
}
wrappedValue = elements
}
}
.
struct ProductList: Decodable {
var products:FailableDecodableArray<Product>
}
struct Product: Decodable {
let id: Int
let title: String
let price: Double
let image: FailableDecodable<Image>?
}
struct Image: Decodable {
let id: Int
let url: String
let thumbnailUrl: String
}
. , .
let products = productsList.products.wrappedValue
let image = products.first?.image.wrappedValue
FailableDecodableArray
. , RandomAccessCollection
MutableCollection
, wrappedValue
. FailableDecodable
. , , computed property
, , . , .
@propertyWrapper
Swift 5.1 — @propertyWrapper
.
@propertyWrapper
struct FailableDecodable<Value: Decodable>: Decodable {
...
}
@propertyWrapper
struct FailableDecodableArray<Value: Decodable>: Decodable {
...
}
struct ProductList: Decodable {
@FailableDecodableArray
var products:[Product]
}
struct Product: Decodable {
let id: Int
let title: String
let price: Double
@FailableDecodable
let image:Image?
}
, wrappedValue
. , , , , :)
, , optional
.
@FailableDecodable
let image:Image?
let image: FailableDecodable<Image>
, optional
Image?
, wrappedValue
optional
, .
Swift
@FailableDecodable?
let image:Image?
, , JSON nil
. @propertyWrapper
, 100% JSON.
@dynamicMemberLookup
dynamicMemberLookup
.
@dynamicMemberLookup
struct FailableDecodable<Value: Decodable>: Decodable {
var wrappedValue: Value?
subscript<Prop>(dynamicMember kp: KeyPath<Value, Prop>) -> Prop {
wrappedValue[keyPath: kp]
}
subscript<Prop>(dynamicMember kp: WritableKeyPath<Value, Prop>) -> Prop {
get {
wrappedValue[keyPath: kp]
}
set {
wrappedValue[keyPath: kp] = newValue
}
}
}
2 subscript
, readonly , read/write . .
struct ProductList: Decodable {
var products:FailableDecodableArray<Product>
}
struct Product: Decodable {
let id: Int
let title: String
let price: Double
let image: FailableDecodable<Image>?
}
@propertyWrapper
wrappedValue
, .
let imageURL = products.first?.image.url
, optional
. , , wrappedValue
, , .
products.first?.image.load()
products.first?.image.wrappedValue.load()
( ), .
— . - , ( JSON) Swift , “1” 1 — . JSONDecoder
, , . . , Product
.
{
"id": 1,
"title": "Awesome product",
"price": "12.2",
"image": {
"id": 1,
"url": "http://image.png",
"thumbnail_url": "http://thumbnail.png"
}
}
, , , — , , , , , . , .
struct Convertible<Value: Decodable & LosslessStringConvertible>: Decodable {
var wrappedValue: Value
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
guard let stringValue = try? container.decode(String.self),
let value = Value(stringValue) else {
wrappedValue = try container.decode(Value.self)
return
}
wrappedValue = value
}
}
struct Product: Decodable {
let id: Int
let title: String
let price: Convertible<Double>
let image: FailableDecodable<Image>?
}
, . FailableDecdable
@propertyWrapper
@dynamicMemberLookup
.
( ) . , , API - , , - . , , , , -, , , -, , . , , .
struct StringConvertible: Decodable {
var wrappedValue: String
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
guard let number = try? container.decode(Double.self) else {
wrappedValue = try container.decode(String.self)
return
}
wrappedValue = "\(number)"
}
}
总结以上所有内容,我想指出的是,协议Decodable
与类的结合JSONDecoder
大大简化了我们与服务器数据解析相关的工作。但是,值得注意的是JSONDecoder
,目前它的忠诚度非常低,要提高忠诚度(如有必要),您需要花点功夫并写一些包装纸。我认为将来所有这些功能都将在对象本身中实现JSONDecoder
,因为相对较新的时间,他甚至不知道如何将键从蛇形转换为驼峰形和字符串转换为日期,现在所有这些都可以“立即可用”。