So schließen Sie Eigenschaften von Swift 4's Codable aus

92

Swift 4 ist neu Encodable/Decodable Protokolle machen JSON (de) Serialisierung sehr angenehm. Ich habe jedoch noch keinen Weg gefunden, genau zu steuern, welche Eigenschaften codiert und welche decodiert werden sollen.

Ich habe festgestellt, dass das Ausschließen der Eigenschaft aus der beigefügten CodingKeysAufzählung die Eigenschaft insgesamt aus dem Prozess ausschließt. Gibt es jedoch eine Möglichkeit, eine genauere Kontrolle zu erzielen?

RamwiseMatt
quelle
Wollen Sie damit sagen, dass Sie einen Fall haben, in dem Sie einige Eigenschaften haben, die Sie codieren möchten, aber verschiedene Eigenschaften, die Sie decodieren möchten? (dh Sie möchten, dass Ihr Typ nicht rund umrundbar ist?) Wenn Sie nur die Eigenschaft ausschließen möchten, reicht es aus, einen Standardwert anzugeben und ihn aus der CodingKeysAufzählung herauszulassen .
Itai Ferber
Unabhängig davon können Sie die Anforderungen des CodableProtokolls ( init(from:)und encode(to:)) jederzeit manuell implementieren, um die vollständige Kontrolle über den Prozess zu erhalten.
Itai Ferber
Mein spezieller Anwendungsfall besteht darin, zu vermeiden, dass ein Decoder zu viel Kontrolle erhält, was dazu führen kann, dass JSON aus der Ferne durch Überschreiben interner Eigenschaftswerte abgerufen wird. Die folgenden Lösungen sind ausreichend!
RamwiseMatt
1
Ich würde gerne eine Antwort / neue Swift-Funktion sehen, bei der nur die Sonderfälle und ausgeschlossenen Schlüssel behandelt werden müssen, anstatt alle Eigenschaften, die Sie normalerweise kostenlos erhalten sollten, erneut zu implementieren.
pkamb

Antworten:

172

Die Liste der zu codierenden / decodierenden Schlüssel wird von einem Typ namens gesteuert CodingKeys(beachten Sie dies am Ende). Der Compiler kann dies für Sie synthetisieren, aber immer überschreiben.

Angenommen, Sie möchten die Eigenschaft nicknamesowohl von der Codierung als auch von der Decodierung ausschließen:

struct Person: Codable {
    var firstName: String
    var lastName: String
    var nickname: String?

    private enum CodingKeys: String, CodingKey {
        case firstName, lastName
    }
}

Wenn Sie möchten, dass es asymmetrisch ist (dh codieren, aber nicht decodieren oder umgekehrt), müssen Sie Ihre eigenen Implementierungen von encode(with encoder: )und bereitstellen init(from decoder: ):

struct Person: Codable {
    var firstName: String
    var lastName: String

    // Since fullName is a computed property, it's excluded by default
    var fullName: String {
        return firstName + " " + lastName
    }

    private enum CodingKeys: String, CodingKey {
        case firstName
        case lastName
        case fullName
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        firstName = try container.decode(String.self, forKey: .firstName)
        lastName = try container.decode(String.self, forKey: .lastName)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(firstName, forKey: .firstName)
        try container.encode(lastName, forKey: .lastName)
        try container.encode(fullName, forKey: .fullName)
    }
}
Code anders
quelle
16
Sie müssen nicknameeinen Standardwert angeben, damit dies funktioniert. Andernfalls kann der Eigenschaft am kein Wert zugewiesen werden init(from:).
Itai Ferber
1
@ItaiFerber Ich habe es auf ein optionales geändert, das ursprünglich in meinem Xcode war
Code Different
Sind Sie sicher, dass Sie das encodeim asymmetrischen Beispiel angeben müssen? Da dies immer noch das Standardverhalten ist, habe ich nicht gedacht, dass es benötigt wird. Nur decodeda kommt die Asymmetrie her.
Mark A. Donohoe
1
@MarqueIV Ja, das musst du. Da fullNameeine gespeicherte Eigenschaft nicht zugeordnet werden kann, müssen Sie einen benutzerdefinierten Codierer und Decodierer bereitstellen.
Code anders
2

Wenn Sie die Dekodierung einiger Eigenschaften aus einer großen Anzahl von Eigenschaften in der Struktur ausschließen müssen, deklarieren Sie sie als optionale Eigenschaften. Code zum Auspacken von Optionen ist weniger als das Schreiben vieler Schlüssel unter CodingKey-Enumeration.

Ich würde empfehlen, Erweiterungen zu verwenden, um berechnete Instanzeigenschaften und berechnete Typeneigenschaften hinzuzufügen. Es trennt codierbare Anpassungseigenschaften von anderer Logik und bietet somit eine bessere Lesbarkeit.

Hrishikesh Devhare
quelle
1

Eine andere Möglichkeit, einige Eigenschaften vom Encoder auszuschließen, ist die Verwendung eines separaten Codierungscontainers

struct Person: Codable {
    let firstName: String
    let lastName: String
    let excludedFromEncoder: String

    private enum CodingKeys: String, CodingKey {
        case firstName
        case lastName
    }
    private enum AdditionalCodingKeys: String, CodingKey {
        case excludedFromEncoder
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let anotherContainer = try decoder.container(keyedBy: AdditionalCodingKeys.self)
        firstName = try container.decode(String.self, forKey: .firstName)
        lastName = try container.decode(String.self, forKey: .lastName)

        excludedFromEncoder = try anotherContainer(String.self, forKey: . excludedFromEncoder)
    }

    // it is not necessary to implement custom encoding
    // func encode(to encoder: Encoder) throws

    // let person = Person(firstName: "fname", lastName: "lname", excludedFromEncoder: "only for decoding")
    // let jsonData = try JSONEncoder().encode(person)
    // let jsonString = String(data: jsonData, encoding: .utf8)
    // jsonString --> {"firstName": "fname", "lastName": "lname"}

}

Der gleiche Ansatz kann für den Decoder verwendet werden

Aleksei Kiselev
quelle
0

Während dies getan werden kann , wird es letztendlich sehr unkompliziert und sogar unjSONy . Ich glaube, ich sehe, woher Sie kommen, das Konzept von #ids ist in HTML weit verbreitet, aber es wird selten in die Welt übertragen, JSONdie ich für eine gute Sache halte (TM).

Einige CodableStrukturen können Ihre JSONDatei problemlos analysieren, wenn Sie sie mit rekursiven Hashes umstrukturieren, dh wenn Ihre recipenur ein Array enthält, ingredientsdas wiederum (eines oder mehrere) enthält ingredient_info. Auf diese Weise hilft Ihnen der Parser, Ihr Netzwerk in erster Linie zusammenzufügen, und Sie müssen nur dann einige Backlinks durch einfaches Durchlaufen der Struktur bereitstellen, wenn Sie sie wirklich benötigen . Da dies eine gründliche Überarbeitung Ihrer JSONund Ihrer Datenstruktur erfordert, skizziere ich nur die Idee, damit Sie darüber nachdenken. Wenn Sie es für akzeptabel halten, teilen Sie es mir bitte in den Kommentaren mit, dann könnte ich es weiter ausführen, aber je nach den Umständen können Sie möglicherweise keine der beiden ändern.

Patru
quelle
0

Ich habe das Protokoll und seine Erweiterung zusammen mit AssociatedObject verwendet, um die Eigenschaft image (oder eine Eigenschaft, die von Codable ausgeschlossen werden muss) festzulegen und abzurufen.

Damit müssen wir keinen eigenen Encoder und Decoder implementieren

Hier ist der Code, der der Einfachheit halber den relevanten Code beibehält:

protocol SCAttachmentModelProtocol{
    var image:UIImage? {get set}
    var anotherProperty:Int {get set}
}
extension SCAttachmentModelProtocol where Self: SCAttachmentUploadRequestModel{
    var image:UIImage? {
        set{
            //Use associated object property to set it
        }
        get{
            //Use associated object property to get it
        }
    }
}
class SCAttachmentUploadRequestModel : SCAttachmentModelProtocol, Codable{
    var anotherProperty:Int
}

Wann immer wir auf die Image-Eigenschaft zugreifen möchten, können wir sie für das Objekt verwenden, das das Protokoll bestätigt (SCAttachmentModelProtocol).

Endlosschleife
quelle
0

Sie können berechnete Eigenschaften verwenden:

struct Person: Codable {
  var firstName: String
  var lastName: String
  var nickname: String?

  var nick: String {
    get {
      nickname ?? ""
    }
  }

  private enum CodingKeys: String, CodingKey {
    case firstName, lastName
  }
}
Beta-Logik
quelle