Come escludere proprietà da Codable di Swift 4


104

I nuovi protocolli Encodable/ di Swift 4 Decodablerendono la (de) serializzazione JSON piuttosto piacevole. Tuttavia, non ho ancora trovato un modo per avere un controllo dettagliato su quali proprietà dovrebbero essere codificate e quali dovrebbero essere decodificate.

Ho notato che l'esclusione della proprietà CodingKeysdall'enumerazione allegata esclude completamente la proprietà dal processo, ma esiste un modo per avere un controllo più dettagliato?


Stai dicendo che hai un caso in cui hai alcune proprietà che vuoi codificare, ma diverse proprietà che vuoi decodificare? (Cioè vuoi che il tuo tipo non sia round tripable?) Perché se ti interessa solo escludere la proprietà, CodingKeysè sufficiente darle un valore predefinito e lasciarlo fuori dall'enum.
Itai Ferber

Indipendentemente da ciò, è sempre possibile implementare i requisiti del Codableprotocollo ( init(from:)e encode(to:)) manualmente per il pieno controllo del processo.
Itai Ferber

Il mio caso d'uso specifico è quello di evitare di dare a un decoder un controllo eccessivo, il che potrebbe portare a JSON ottenuto in remoto dalla sovrascrittura dei valori delle proprietà interne. Le soluzioni seguenti sono adeguate!
RamwiseMatt

1
Mi piacerebbe vedere una risposta / una nuova funzionalità Swift che richiede solo la gestione dei casi speciali e delle chiavi escluse, piuttosto che reimplementare tutte le proprietà che normalmente dovresti ottenere gratuitamente.
pkamb

Risposte:


182

L'elenco delle chiavi da codificare / decodificare è controllato da un tipo chiamato CodingKeys(nota salla fine). Il compilatore può sintetizzarlo per te ma può sempre sovrascriverlo.

Supponiamo che tu voglia escludere la proprietà nicknamesia dalla codifica che dalla decodifica:

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

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

Se vuoi che sia asimmetrico (cioè codificare ma non decodificare o viceversa), devi fornire le tue implementazioni di encode(with encoder: )e 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)
    }
}

17
È necessario fornire nicknameun valore predefinito affinché funzioni. In caso contrario, non è possibile assegnare alcun valore alla proprietà init(from:).
Itai Ferber

1
@ItaiFerber L'ho passato a un opzionale, che era originariamente nel mio Xcode
Code Different,

Sei sicuro di dover fornire l' encodeesempio asimmetrico? Poiché questo è ancora il comportamento standard, non pensavo fosse necessario. Solo decodeperché è da lì che proviene l'asimmetria.
Mark A. Donohoe

1
@MarqueIV Sì, devi. Poiché fullNamenon è possibile mappare una proprietà memorizzata, è necessario fornire un codificatore e un decodificatore personalizzati.
Codice diverso

2

Se è necessario escludere la decodifica di un paio di proprietà da un ampio insieme di proprietà nella struttura, dichiararle come proprietà opzionali. Il codice per scartare gli optionals è meno che scrivere molte chiavi sotto CodingKey enum.

Suggerirei di utilizzare le estensioni per aggiungere proprietà di istanza calcolate e proprietà di tipo calcolate. Separa le proprietà di formattazione codificabili da altra logica, quindi fornisce una migliore leggibilità.


2

Un altro modo per escludere alcune proprietà dal codificatore, è possibile utilizzare un contenitore di codifica separato

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"}

}

lo stesso approccio può essere utilizzato per il decoder


1

È possibile utilizzare proprietà calcolate:

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
  }
}

Questo era l'indizio per me: l'utilizzo di una proprietà lazy varche lo rendeva effettivamente una proprietà runtime lo escludeva da Codable.
ChrisH

0

Anche se questo può essere fatto, alla fine finisce per essere molto poco swifty e persino unJSONy . Penso di capire da dove vieni, il concetto di #ids è prevalente in HTML, ma raramente viene trasportato nel mondo di JSONcui considero una buona cosa (TM).

Alcune Codablestrutture saranno in grado di analizzare bene il tuo JSONfile se lo ristrutturi usando hash ricorsivi, cioè se il tuo recipecontiene solo un array di ingredientscui a sua volta contiene (uno o più) ingredient_info. In questo modo il parser ti aiuterà a ricucire la tua rete in primo luogo e dovrai solo fornire alcuni backlink attraverso un semplice attraversamento della struttura se ne hai davvero bisogno . Poiché ciò richiede una rielaborazione completa della tua JSONe della tua struttura dati, ho solo abbozzato l'idea per farti riflettere. Se lo ritieni accettabile, dimmelo nei commenti, quindi potrei elaborarlo ulteriormente, ma a seconda delle circostanze potresti non essere libero di modificare nessuno dei due.


0

Ho usato il protocollo e la sua estensione insieme ad AssociatedObject per impostare e ottenere la proprietà dell'immagine (o qualsiasi proprietà che deve essere esclusa da Codable).

Con questo non dobbiamo implementare il nostro codificatore e decodificatore

Ecco il codice, mantenendo il codice pertinente per semplicità:

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
}

Ora, ogni volta che vogliamo accedere alla proprietà Image possiamo usare sull'oggetto la conferma al protocollo (SCAttachmentModelProtocol)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.