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 Decodable
et 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 JSONDecoder
fera 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,JSONDecoder
jette 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()
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)"
}
}
Résumant tout ce qui précède, je voudrais noter que l'émergence du protocole Decodable
conjointement avec la classe a JSONDecoder
considérablement simplifié notre vie dans ce qui est associé à l'analyse des données du serveur. Cependant, il convient de noter qu'à JSONDecoder
l'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".