在iOS中提高服务器数据解析器的忠诚度

在开发移动应用程序时,我们面临着将服务器数据解析为内部应用程序模型的需求。在大多数情况下,此数据采用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() // Compilation error
products.first?.image.wrappedValue.load() // Success

( ), .



— . - , ( 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,因为相对较新的时间,他甚至不知道如何将键从蛇形转换为驼峰形和字符串转换为日期,现在所有这些都可以“立即可用”。


All Articles