Augmenter la fidélité de l'analyseur de données du serveur dans iOS

Lors du développement d'applications mobiles, d'une manière ou d'une autre, nous sommes confrontés à la nécessité d'analyser les données du serveur dans des modèles d'application internes. Dans la grande majorité des cas, ces données sont au format JSON. À partir de Swift 4, l'outil principal pour analyser JSON est d'utiliser un protocole Decodableet un objet JSONDecoder.


Cette approche a grandement simplifié le processus d'analyse des données et réduit le nombre de codes passe-partout. Dans la plupart des cas, il est assez simple de créer des modèles avec des propriétés nommées comme des champs dans l'objet JSON et JSONDecoderfera le reste pour vous. Code minimum, avantage maximum. Cependant, cette approche présente un inconvénient, à savoir la fidélité extrêmement faible de l'analyseur. Je vais expliquer. En cas de divergence entre le modèle de données interne (objets décodables) et ce qui est venu dans JSON,JSONDecoderjette une erreur et nous perdons tout l'objet. Dans certaines situations, ce modèle de comportement est peut-être préférable, en particulier lorsqu'il s'agit, par exemple, de transactions financières. Mais dans de nombreux cas, il serait utile de rendre le processus d'analyse plus fidèle. Dans cet article, je voudrais partager mon expérience et parler des principaux moyens d'augmenter cette même fidélité.


Filtrage des objets invalides


Eh bien, le premier élément est, bien sûr, le filtrage des objets invalides. Dans de nombreuses situations, nous ne voulons pas perdre l'objet entier si l'un des objets imbriqués n'est pas valide. Cela s'applique à la fois aux objets uniques et aux tableaux d'objets. Je vais vous donner un exemple. Supposons que nous fassions une demande de vente de marchandises et sur l'un des écrans nous obtenons une liste de marchandises sous cette forme approximativement.


{
    "products": [
       {...},
       {...},
       ....
    ]
}

, . . , , - . , JSONDecoder “ ” .


, :


{
    "id": 1,
    "title": "Awesome product",
    "price": 12.2,
    "image": {
        "id": 1,
        "url": "http://image.png",
        "thumbnail_url": "http://thumbnail.png"
    }
}

, , image. , , 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)"
    }
}

Résumant tout ce qui précède, je voudrais noter que l'émergence du protocole Decodableconjointement avec la classe a JSONDecoderconsidérablement simplifié notre vie dans ce qui est associé à l'analyse des données du serveur. Cependant, il convient de noter qu'à JSONDecoderl'heure actuelle, il a une fidélité extrêmement faible et pour l'améliorer (si nécessaire), vous devez travailler un peu et écrire quelques emballages. Je pense qu'à l'avenir toutes ces fonctionnalités seront réalisées dans l'objet lui JSONDecoder- même , car relativement récemment, il ne savait même pas comment convertir les clés de snakecase en camelcase et de chaîne à ce jour, et maintenant tout cela est disponible "prêt à l'emploi".


All Articles