Bei der Verwendung der Protokolle Swift4 und Codable trat das folgende Problem auf: Es gibt anscheinend keine Möglichkeit, das JSONDecoder
Überspringen von Elementen in einem Array zuzulassen . Zum Beispiel habe ich den folgenden JSON:
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
Und eine codierbare Struktur:
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
Beim Dekodieren dieses JSON
let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)
Das Ergebnis products
ist leer. Dies ist zu erwarten, da das zweite Objekt in JSON keinen "points"
Schlüssel hat, während points
es in GroceryProduct
struct nicht optional ist .
Die Frage ist, wie ich zulassen kann, dass ein JSONDecoder
ungültiges Objekt "übersprungen" wird.
points
einfach als optional deklariert werden?Antworten:
Eine Möglichkeit besteht darin, einen Wrapper-Typ zu verwenden, der versucht, einen bestimmten Wert zu dekodieren. Speichern,
nil
wenn nicht erfolgreich:Wir können dann ein Array davon dekodieren,
GroceryProduct
indem Sie denBase
Platzhalter ausfüllen :Wir verwenden dann
.compactMap { $0.base }
, umnil
Elemente herauszufiltern (diejenigen, die beim Decodieren einen Fehler verursacht haben).Dadurch wird ein Zwischenarray von erstellt
[FailableDecodable<GroceryProduct>]
, das kein Problem darstellen sollte. Wenn Sie dies jedoch vermeiden möchten, können Sie jederzeit einen anderen Wrapper-Typ erstellen, der jedes Element aus einem nicht verschlüsselten Container dekodiert und entpackt:Sie würden dann dekodieren als:
quelle
var container = try decoder.unkeyedContainer()
init(from:) throws
, sodass Swift den Fehler automatisch an den Anrufer zurücksendet (in diesem Fall an den Decoder, der ihn an denJSONDecoder.decode(_:from:)
Anruf zurückgibt ).Ich würde einen neuen Typ erstellen
Throwable
, der jeden Typ umschließen kann, der entsprichtDecodable
:Zum Dekodieren eines Arrays von
GroceryProduct
(oder einem anderenCollection
):wobei
value
eine berechnete Eigenschaft in einer Erweiterung eingeführt aufThrowable
:Ich würde mich für die Verwendung eines
enum
Wrapper-Typs (über aStruct
) entscheiden, da es nützlich sein kann, die ausgelösten Fehler sowie deren Indizes zu verfolgen.Swift 5
Für Swift 5 Verwenden Sie z
Result
enum
Verwenden Sie die
get()
Methode für dieresult
Eigenschaft, um den dekodierten Wert zu entpacken :quelle
init
Das Problem ist, dass beim Durchlaufen eines Containers der container.currentIndex nicht inkrementiert wird, sodass Sie versuchen können, erneut mit einem anderen Typ zu dekodieren.
Da der aktuelle Index schreibgeschützt ist, besteht eine Lösung darin, ihn selbst zu erhöhen und einen Dummy erfolgreich zu dekodieren. Ich nahm die @ Hamish-Lösung und schrieb einen Wrapper mit einem benutzerdefinierten Init.
Dieses Problem ist ein aktueller Swift-Fehler: https://bugs.swift.org/browse/SR-5953
Die hier veröffentlichte Lösung ist eine Problemumgehung in einem der Kommentare. Diese Option gefällt mir, weil ich eine Reihe von Modellen auf die gleiche Weise auf einem Netzwerkclient analysiere und wollte, dass die Lösung für eines der Objekte lokal ist. Das heißt, ich möchte immer noch, dass die anderen verworfen werden.
Ich erkläre es besser in meinem Github https://github.com/phynet/Lossy-array-decode-swift4
quelle
if/else
ich einedo/catch
innerhalb derwhile
Schleife, damit ich den Fehler protokollieren kannEs gibt zwei Möglichkeiten:
Deklarieren Sie alle Mitglieder der Struktur als optional, deren Schlüssel fehlen können
Schreiben Sie einen benutzerdefinierten Initialisierer, um in diesem
nil
Fall Standardwerte zuzuweisen .quelle
try?
mit istdecode
es besser,try
mitdecodeIfPresent
in der zweiten Option zu verwenden. Wir müssen den Standardwert nur festlegen, wenn kein Schlüssel vorhanden ist, nicht im Falle eines Dekodierungsfehlers, z. B. wenn ein Schlüssel vorhanden ist, der Typ jedoch falsch ist.deviceName = try values.decodeIfPresent(Int.self, forKey: .deviceName) ?? 00000
Wenn dies fehlschlägt, wird nur 0000 eingegeben, aber es schlägt immer noch fehl.decodeIfPresent
ist das falsch,API
weil der Schlüssel existiert. Verwenden Sie einen anderendo - catch
Block. DecodeString
, wenn ein Fehler auftritt, zu dekodierenInt
Eine Lösung, die Swift 5.1 mithilfe des Eigenschafts-Wrappers ermöglicht:
Und dann die Verwendung:
Hinweis: Der Property Wrapper funktioniert nur, wenn die Antwort in eine Struktur eingeschlossen werden kann (dh kein Array der obersten Ebene). In diesem Fall können Sie es weiterhin manuell umbrechen (mit einem Typealias zur besseren Lesbarkeit):
quelle
Ich habe die @ sophy-swicz-Lösung mit einigen Modifikationen in eine benutzerfreundliche Erweiterung integriert
Nennen Sie es einfach so
Für das obige Beispiel:
quelle
Leider hat die Swift 4 API keinen fehlgeschlagenen Initialisierer für
init(from: Decoder)
.Ich sehe nur eine Lösung, die die benutzerdefinierte Dekodierung implementiert und einen Standardwert für optionale Felder und einen möglichen Filter mit den erforderlichen Daten angibt:
quelle
Ich hatte kürzlich ein ähnliches Problem, aber etwas anders.
In diesem Fall
friendnamesArray
ist das gesamte Objekt beim Decodieren Null , wenn eines der Elemente in Null ist.Und der richtige Weg , diese Kante Fall zu behandeln , ist das String - Array zu deklarieren
[String]
als Array von optionalen Strings[String?]
wie unten,quelle
Ich habe @ Hamish's für den Fall verbessert, dass Sie dieses Verhalten für alle Arrays wünschen:
quelle
@ Hamishs Antwort ist großartig. Sie können jedoch Folgendes reduzieren
FailableCodableArray
:quelle
Stattdessen können Sie auch Folgendes tun:
und dann rein, während du es bekommst:
quelle
Ich habe mir das ausgedacht
KeyedDecodingContainer.safelyDecodeArray
, das eine einfache Oberfläche bietet:Die potenziell unendliche Schleife
while !container.isAtEnd
ist ein Problem und wird mithilfe von behobenEmptyDecodable
.quelle
Ein viel einfacherer Versuch: Warum deklarieren Sie Punkte nicht als optional oder lassen das Array optionale Elemente enthalten?
quelle