with - swift json decoder array




Können mit JSONDecoder in Swift 4 fehlende Schlüssel einen Standardwert verwenden, anstatt optionale Eigenschaften sein zu müssen? (4)

Swift 4 hat das neue Codable Protokoll hinzugefügt. Wenn ich JSONDecoder verwende, JSONDecoder anscheinend alle nicht optionalen Eigenschaften meiner Codable Klasse Schlüssel im JSON enthalten sein, oder es wird ein Fehler Codable .

Jede Eigenschaft meiner Klasse optional zu machen, scheint ein unnötiger Aufwand zu sein, da ich wirklich den Wert in json oder einen Standardwert verwenden möchte. (Ich möchte nicht, dass die Eigenschaft null ist.)

Gibt es eine Möglichkeit, dies zu tun?

class MyCodable: Codable {
    var name: String = "Default Appleseed"
}

func load(input: String) {
    do {
        if let data = input.data(using: .utf8) {
            let result = try JSONDecoder().decode(MyCodable.self, from: data)
            print("name: \(result.name)")
        }
    } catch  {
        print("error: \(error)")
        // `Error message: "Key not found when expecting non-optional type
        // String for coding key \"name\""`
    }
}

let goodInput = "{\"name\": \"Jonny Appleseed\" }"
let badInput = "{}"
load(input: goodInput) // works, `name` is Jonny Applessed
load(input: badInput) // breaks, `name` required since property is non-optional

Eine Lösung wäre die Verwendung einer berechneten Eigenschaft, die standardmäßig den gewünschten Wert verwendet, wenn der JSON-Schlüssel nicht gefunden wird. Dies CodingKeys Ausführlichkeit, da Sie eine andere Eigenschaft deklarieren und die CodingKeys hinzufügen CodingKeys (falls nicht bereits vorhanden). Der Vorteil ist, dass Sie keinen benutzerdefinierten Dekodierungs- / Kodierungscode schreiben müssen.

Zum Beispiel:

final class CodableModel: Codable
{
    static func customDecode(_ obj: [String: Any]) -> CodableModel?
    {
        var validatedDict = obj
        let someField = validatedDict[CodingKeys.someField.stringValue] ?? false
        validatedDict[CodingKeys.someField.stringValue] = someField

        guard
            let data = try? JSONSerialization.data(withJSONObject: validatedDict, options: .prettyPrinted),
            let model = try? CodableModel.decoder.decode(CodableModel.self, from: data) else {
                return nil
        }

        return model
    }

    //your coding keys, properties, etc.
}

Ich bevorzuge den Ansatz, sogenannte DTOs (Data Transfer Object) zu verwenden. Es ist eine Struktur, die Codable entspricht und das gewünschte Objekt darstellt.

class MyCodable: Codable {
    var name: String = "Default Appleseed"

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let name = try container.decodeIfPresent(String.self, forKey: .name) {
            self.name = name
        }
    }
}

Dann initialisieren Sie einfach das Objekt, das Sie in der App verwenden möchten, mit diesem DTO.

class MyCodable: Codable {
    let name: String

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let name = try container.decodeIfPresent(String.self, forKey: .name) {
            self.name = name
        } else {
            self.name = "Default Appleseed"
        }
    }
}

Dieser Ansatz ist auch gut, da Sie das endgültige Objekt nach Belieben umbenennen und ändern können. Es ist klar und erfordert weniger Code als die manuelle Dekodierung. Darüber hinaus können Sie mit diesem Ansatz die Netzwerkebene von anderen Apps trennen.


Sie können implementieren.

do {
    let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
    let model = try CodableModel.decoder.decode(CodableModel.self, from: data)                        
} catch {
    assertionFailure(error.localizedDescription)
}

Wenn Sie Ihre Kodierungs- und Dekodierungsmethoden nicht implementieren möchten, gibt es eine etwas unsaubere Lösung für Standardwerte.

Sie können Ihr neues Feld als implizit entpackt deklarieren und prüfen, ob es nach der Dekodierung null ist, und einen Standardwert festlegen.

Ich habe dies nur mit PropertyListEncoder getestet, aber ich denke, JSONDecoder funktioniert genauso.







codable