Generischer JSONDecoder

Derzeit ist die überwiegende Mehrheit der mobilen Anwendungen Client-Server. Überall wird geladen, synchronisiert, Ereignisse gesendet und die Hauptmethode für die Interaktion mit dem Server besteht darin, Daten im JSON-Format auszutauschen.


Schlüsseldecodierung


Die Stiftung verfügt über zwei Mechanismen zur Serialisierung von toten Daten. Alt ist NSJsonSerialization und neu ist Codable . Der letzte in der Liste der Vorteile enthält eine wunderbare Funktion wie die automatische Generierung von Schlüsseln für JSON-Daten auf der Grundlage einer Struktur (oder Klasse), die Codable ( Encodable , Decodable ) und einen Initialisierer zum Decodieren von Daten implementiert.


Und alles scheint in Ordnung zu sein, man kann es benutzen und genießen, aber die Realität ist nicht so einfach.
Sehr oft sieht man auf dem Server json des Formulars:


 {"topLevelObject": { "underlyingObject": 1 }, "Error": { "ErrorCode": 400, "ErrorDescription": "SomeDescription" } } 

Dies ist ein fast reales Beispiel von einem der Projektserver.


Für die JsonDecoder Klasse können JsonDecoder die Arbeit mit snake_case-Schlüsseln angeben. Was ist jedoch, wenn wir UpperCamelCase, Dash-Snake-Case oder sogar ein allgemeines Durcheinander haben, die Schlüssel jedoch nicht manuell schreiben möchten?


Glücklicherweise bot Apple die Möglichkeit, die Schlüsselzuordnung zu konfigurieren, bevor sie mit CodingKeys mit der CodingKeys Struktur JSONDecoder.KeyDecodingStrategy . Wir werden dies nutzen.


Erstellen Sie zunächst eine Struktur, die das CodingKey Protokoll implementiert, da die Standardbibliothek keine enthält:


  struct AnyCodingKey: CodingKey { var stringValue: String var intValue: Int? init(_ base: CodingKey) { self.init(stringValue: base.stringValue, intValue: base.intValue) } init(stringValue: String) { self.stringValue = stringValue } init(intValue: Int) { self.stringValue = "\(intValue)" self.intValue = intValue } init(stringValue: String, intValue: Int?) { self.stringValue = stringValue self.intValue = intValue } } 

Dann ist es notwendig, jeden Fall unserer Schlüssel separat zu bearbeiten. Die wichtigsten:
snake_case, dash-snake-case, lowerCamelCase und UpperCamelCase. Überprüfen, ausführen, alles funktioniert.


Dann stoßen wir auf ein eher erwartetes Problem: Abkürzungen in camelCase'ah (erinnern Sie sich an die zahlreichen ID , ID , ID ). Damit es funktioniert, müssen Sie sie korrekt konvertieren und eine Regel einführen. Abkürzungen werden in camelCase konvertiert, wobei nur der Großbuchstabe beibehalten wird und myABBRKey in myAbbrKey umgewandelt wird .


Diese Lösung eignet sich hervorragend für Kombinationen aus mehreren Fällen.


Hinweis: Die Implementierung erfolgt in. .custom Schlüsseldecodierungsstrategie.


 static func convertToProperLowerCamelCase(keys: [CodingKey]) -> CodingKey { guard let last = keys.last else { assertionFailure() return AnyCodingKey(stringValue: "") } if let fromUpper = convertFromUpperCamelCase(initial: last.stringValue) { return AnyCodingKey(stringValue: fromUpper) } else if let fromSnake = convertFromSnakeCase(initial: last.stringValue) { return AnyCodingKey(stringValue: fromSnake) } else { return AnyCodingKey(last) } } 

Datumsdecodierung


Das nächste Routineproblem ist die Art und Weise, wie Daten übergeben werden. Es gibt viele Microservices auf dem Server, es gibt etwas weniger Teams, aber auch eine anständige Menge, und am Ende stehen wir vor einer Reihe von Datumsformaten wie „Ja, ich verwende den Standard“. Außerdem übergibt jemand Daten in einer Zeichenfolge, jemand in der Epochenzeit. Infolgedessen gibt es wieder ein Durcheinander von Kombinationen aus Zeichenfolge, Zeitzone, Millisekunde und Trennzeichen, und DateDecoder in iOS beschwert sich und erfordert ein striktes Datumsformat. Die Lösung hier ist einfach: Durch Suchen suchen wir nach Zeichen eines bestimmten Formats und kombinieren sie, um das erforderliche Ergebnis zu erzielen. Diese Formate haben meine Fälle erfolgreich und vollständig abgedeckt.


Hinweis: Dies ist ein benutzerdefinierter DateFormatter-Initialisierer. Das Format ist nur auf den erstellten Formatierer eingestellt.


 static let onlyDate = DateFormatter(format: "yyyy-MM-dd") static let full = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss.SSSx") static let noWMS = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ssZ") static let noWTZ = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss.SSS") static let noWMSnoWTZ = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss") 

Wir hängen dies mit JSONDecoder.DateDecodingStrategy an unseren Decoder JSONDecoder.DateDecodingStrategy und erhalten einen Decoder, der fast alles verarbeitet und in ein für uns verdauliches Format konvertiert.


Leistungstests


Es wurden Tests für JSON-Zeichenfolgen mit einer Größe von 7944 Byte durchgeführt.


convertFromSnakeCase-StrategieanyCodingKey-Strategie
Absolut0,001700,00210
Relativ81%100%

Wie wir sehen können, ist der benutzerdefinierte Decoder 20% langsamer, da jeder Schlüssel in json auf die Notwendigkeit einer Transformation überprüft werden muss. Dies ist jedoch eine geringe Gebühr, da Schlüssel nicht explizit für Datenstrukturen durch Implementierung von Codable registriert werden Codable . Durch die Hinzufügung dieses Decoders wird die Anzahl der Kesselplatten im Projekt stark reduziert. Sollte ich damit Entwicklerzeit sparen, aber die Leistung verschlechtern? Es liegt an dir.


Vollständiger Beispielcode in der Github-Bibliothek


Englischer Artikel

Source: https://habr.com/ru/post/de449870/


All Articles