Bei der Entwicklung mobiler Anwendungen auf die eine oder andere Weise mĂŒssen wir Serverdaten in interne Anwendungsmodelle zerlegen. In den allermeisten FĂ€llen liegen diese Daten im JSON-Format vor. Ab Swift 4 besteht das Hauptwerkzeug zum Parsen von JSON darin, ein Protokoll Decodable
und ein Objekt zu verwenden JSONDecoder
.
Dieser Ansatz vereinfachte das Parsen von Daten erheblich und reduzierte die Anzahl der Boilerplate-Codes. In den meisten FĂ€llen reicht es aus, einfach Modelle mit Eigenschaften zu erstellen, die genau wie die Felder im JSON-Objekt benannt sind, und JSONDecoder
den Rest fĂŒr Sie zu erledigen. Minimaler Code, maximaler Nutzen. Dieser Ansatz hat jedoch einen Nachteil, nĂ€mlich die extrem geringe Parser-LoyalitĂ€t. Ich werde erklĂ€ren. Wenn es eine Diskrepanz zwischen dem internen Datenmodell (decodierbare Objekte) und dem, was in JSON geliefert wurde, gibt,JSONDecoder
wirft einen Fehler und wir verlieren das ganze Objekt. In einigen Situationen ist dieses Verhaltensmodell möglicherweise vorzuziehen, insbesondere wenn es beispielsweise um Finanztransaktionen geht. In vielen FĂ€llen wĂ€re es jedoch nĂŒtzlich, den Analyseprozess loyaler zu gestalten. In diesem Artikel möchte ich meine Erfahrungen teilen und ĂŒber die wichtigsten Möglichkeiten sprechen, um diese LoyalitĂ€t zu erhöhen.
UngĂŒltige Objekte filtern
Nun, das erste Element ist natĂŒrlich das Filtern ungĂŒltiger Objekte. In vielen Situationen möchten wir nicht das gesamte Objekt verlieren, wenn eines der verschachtelten Objekte ungĂŒltig ist. Dies gilt sowohl fĂŒr einzelne Objekte als auch fĂŒr Arrays von Objekten. Ich werde ein Beispiel geben. Angenommen, wir beantragen den Verkauf von Waren und erhalten auf einem der Bildschirme eine Warenliste in ungefĂ€hr dieser Form.
{
"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)"
}
}
Zusammenfassend möchte ich darauf hinweisen, dass die Entstehung des Protokolls Decodable
in Verbindung mit der Klasse JSONDecoder
unser Leben in Bezug auf das Parsen von Serverdaten erheblich vereinfacht hat. Es ist jedoch erwÀhnenswert, dass es JSONDecoder
im Moment eine extrem geringe LoyalitĂ€t aufweist. Um es zu verbessern (falls erforderlich), mĂŒssen Sie ein wenig arbeiten und ein paar Wrapper schreiben. Ich denke, dass all diese Funktionen in Zukunft im Objekt selbst realisiert werden JSONDecoder
, da er vor relativ kurzer Zeit nicht einmal wusste, wie man SchlĂŒssel von Schlangenkoffer zu Kamelkoffer und Schnur konvertiert, und jetzt ist all dies "out of the box" verfĂŒgbar.