Cómo excluir propiedades de Swift 4 Codable

104

Los nuevos protocolos Encodable/ de Swift 4 Decodablehacen que la (des) serialización de JSON sea bastante agradable. Sin embargo, todavía no he encontrado una manera de tener un control detallado sobre qué propiedades deben codificarse y cuáles deben decodificarse.

He notado que excluir la propiedad de la CodingKeysenumeración adjunta excluye la propiedad del proceso por completo, pero ¿hay alguna manera de tener un control más detallado?

RamwiseMatt
fuente
¿Está diciendo que tiene un caso en el que tiene algunas propiedades que desea codificar, pero diferentes propiedades que desea decodificar? (es decir, ¿desea que su tipo no sea de ida y vuelta?) Porque si solo le importa excluir la propiedad, darle un valor predeterminado y dejarlo fuera de la CodingKeysenumeración es suficiente.
Itai Ferber
Independientemente, siempre puede implementar los requisitos del Codableprotocolo ( init(from:)y encode(to:)) manualmente para un control total sobre el proceso.
Itai Ferber
Mi caso de uso específico es evitar darle demasiado control a un decodificador, lo que podría llevar a JSON obtenido de forma remota al sobrescribir los valores de propiedad internos. ¡Las siguientes soluciones son adecuadas!
RamwiseMatt
1
Me gustaría ver una respuesta / nueva función de Swift que solo requiere manejar los casos especiales y las claves excluidas, en lugar de volver a implementar todas las propiedades que normalmente debería obtener de forma gratuita.
pkamb

Respuestas:

182

La lista de claves para codificar / decodificar está controlada por un tipo llamado CodingKeys(observe el sal final). El compilador puede sintetizar esto por usted, pero siempre puede anularlo.

Supongamos que desea excluir la propiedad nicknametanto de la codificación como de la decodificación:

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

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

Si desea que sea asimétrico (es decir, codificar pero no decodificar o viceversa), debe proporcionar sus propias implementaciones de encode(with encoder: )y 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)
    }
}
Código diferente
fuente
17
Necesita dar nicknameun valor predeterminado para que esto funcione. De lo contrario, no hay ningún valor que se pueda asignar a la propiedad en init(from:).
Itai Ferber
1
@ItaiFerber Lo cambié a un opcional, que estaba originalmente en mi Xcode
Code Different
¿Está seguro de que debe proporcionar el encodeen el ejemplo asimétrico? Dado que ese sigue siendo el comportamiento estándar, no pensé que fuera necesario. Solo decodeporque de ahí es de donde viene la asimetría.
Mark A. Donohoe
1
@MarqueIV Sí, tienes que hacerlo. Dado fullNameque no se puede asignar a una propiedad almacenada, debe proporcionar un codificador y descodificador personalizados.
Código diferente
2

Si necesitamos excluir la decodificación de un par de propiedades de un gran conjunto de propiedades en la estructura, declárelas como propiedades opcionales. El código para desenvolver opcionales es menor que escribir muchas claves en CodingKey enum.

Recomendaría usar extensiones para agregar propiedades de instancia calculadas y propiedades de tipo calculado. Separa las propiedades de conformidad codificables de otras lógicas, por lo que proporciona una mejor legibilidad.

Hrishikesh Devhare
fuente
2

Otra forma de excluir algunas propiedades del codificador, se puede usar un contenedor de codificación separado

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

}

se puede utilizar el mismo enfoque para el decodificador

Aleksei Kiselev
fuente
1

Puede utilizar propiedades calculadas:

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-Lógicas
fuente
Esta fue la pista para mí: usar una lazy varpropiedad de tiempo de ejecución que la convierte efectivamente la excluyó de Codable.
ChrisH
0

Si bien esto se puede hacer, en última instancia, termina siendo muy poco inteligente e incluso poco inteligente . Creo que veo de dónde viene, el concepto de #ids prevalece en HTML, pero rara vez se transporta al mundo JSONque considero algo bueno (TM).

Algunas Codableestructuras podrán analizar su JSONarchivo sin problemas si lo reestructura utilizando hashes recursivos, es decir, si recipesolo contiene una matriz ingredientsque a su vez contiene (uno o varios) ingredient_info. De esa manera, el analizador te ayudará a unir tu red en primer lugar y solo tendrás que proporcionar algunos backlinks a través de un simple recorrido de la estructura si realmente los necesitas . Dado que esto requiere una reelaboración completa de su estructura de datos JSONy la suya, solo esbozo la idea para que la piense. Si lo considera aceptable, por favor dígame en los comentarios, entonces podría desarrollarlo más, pero dependiendo de las circunstancias, es posible que no tenga la libertad de cambiar ninguno de ellos.

Patru
fuente
0

He usado el protocolo y su extensión junto con AssociatedObject para establecer y obtener la propiedad de la imagen (o cualquier propiedad que deba excluirse de Codable).

Con esto no tenemos que implementar nuestro propio codificador y decodificador

Aquí está el código, manteniendo el código relevante para simplificar:

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
}

Ahora, siempre que queramos acceder a la propiedad Image podemos usar en el objeto confirmando el protocolo (SCAttachmentModelProtocol)

Bucle infinito
fuente