Codificable: manejo de valores de enumeración incorrectos

May 09 2023
Salvaguardar su base de código: lidiar con valores de enumeración no anticipados ¿Alguna vez se enfrentó a una situación en la que su aplicación comenzó a fallar de la nada, lo que luego de la investigación arroja que el backend proporcionó un valor para cierto campo basado en enumeración que no existe dentro de la enumeración en el código? Considere el siguiente ejemplo: Ohhh, me encanta un poco de té, pero ¿qué pasa si cambiamos la bebida favorita en los datos JSON de té a jugo (un valor que no existe dentro de la enumeración, bebida)? Concéntrese en la parte del mensaje: "No se puede inicializar la bebida de jugo de valor de cadena no válido" Si fuera una aplicación real en lugar de un código de patio de recreo, la aplicación se habría bloqueado. Entonces, ¿cómo nos ocupamos del hecho de que incluso si el backend envía un valor no válido en la respuesta, nuestra aplicación debería poder manejarlo en lugar de fallar por completo? Una de las maneras, y mi favorito personal:

Protección de su base de código: lidiar con valores de enumeración imprevistos

Foto de Kostiantyn Li en Unsplash

¿Alguna vez se enfrentó a una situación en la que su aplicación comenzó a fallar de la nada, lo que luego de la investigación arroja que el backend proporcionó un valor para cierto campo basado en enumeración que no existe dentro de la enumeración en el código?

Considere el siguiente ejemplo:

import Foundation

struct Person: Codable {
    let name: String
    let favouriteBeverage: Beverage
}

enum Beverage: String, Codable {
    case Tea
    case Coffee
}

let jsonString = """
{
    "name": "Shubham Bakshi",
    "favouriteBeverage": "Tea"
}
""".data(using: .utf8)!

let decodedJSON = try! JSONDecoder().decode(Person.self, from: jsonString)

switch decodedJSON.favouriteBeverage {
    
case .Tea:
    print("Ohhh I love some Tea")
    
case .Coffee:
    print("Coffee is no Tea but meh, I'll take it")
}

Ohhh me encanta un poco de té

Pero, ¿qué sucede si cambiamos favouriteBeveragelos datos JSON de a jugo (un valor que no existe dentro de la enumeración, bebida )?

let jsonString = """
{
    "name": "Shubham Bakshi",
    "favouriteBeverage": "Juice"
}
""".data(using: .utf8)!

Concéntrese en la parte del mensaje: "No se puede inicializar la bebida de jugo de valor de cadena no válido"

Si fuera una aplicación real en lugar de un código de juegos, la aplicación se habría bloqueado. Entonces, ¿cómo nos ocupamos del hecho de que incluso si el backend envía un valor no válido en la respuesta, nuestra aplicación debería poder manejarlo en lugar de fallar por completo ?

Una de las formas, y mi favorito personal: mecanismo de respaldo de enumeración incorrecto.

Dice mecanismo pero es más o menos un protocolo que se encarga de todo. Así que, aquí vamos ….

/// This is useful in case the backend specifies some value that does not 
/// exist inside the enum as in such cases the app crashes since it fails to 
/// parse the enum.
/// 
/// This ensures that in case an invalid value is specified, the app will 
/// not crash but rather resort to a value specified for 
/// `fallbackForIncorrectEnumValue`
///
protocol IncorrectEnumFallbackMechanism: RawRepresentable, Codable {
    
    /// Fallback value in case the backend returns an incorrect value for 
    /// the enum
    static var fallbackForIncorrectEnumValue: Self { get }
}

extension IncorrectEnumFallbackMechanism where RawValue: Codable {
    
    init(from decoder: Decoder) throws {
        
        // Fetching the value specified inside the JSON
        let value = try decoder.singleValueContainer().decode(RawValue.self)
        
        // Creating the instance based on the provided value or falling back 
        // to a default value in case the provided value for creating is 
        // invalid
        self = Self(rawValue: value) ?? Self.fallbackForIncorrectEnumValue
    }
    
    /// Since `RawRepresentable` declares a `encode(coder:)` by default so we 
    /// don't need to implement that explicitly
}

enum Beverage: String, IncorrectEnumFallbackMechanism {
    
    static var fallbackForIncorrectEnumValue: Beverage = .SomeWeirdBeverage
    
    case Tea
    case Coffee
    
    case SomeWeirdBeverage
}

¡Esperar! ¿Qué es eso?

No importa cómo nos envíe el backend favouriteBeverage, ¡seremos capaces de manejarlo o beberlo, lo que sea!

Así que nuestro código completo ahora se verá así:

import Foundation

/// This is useful in case the backend specifies some value that does not
/// exist inside the enum as in such cases the app crashes since it fails to
/// parse the enum.
///
/// This ensures that in case an invalid value is specified, the app will
/// not crash but rather resort to a value specified for
/// `fallbackForIncorrectEnumValue`
///
protocol IncorrectEnumFallbackMechanism: RawRepresentable, Codable {
    
    /// Fallback value in case the backend returns an incorrect value for
    /// the enum
    static var fallbackForIncorrectEnumValue: Self { get }
}

extension IncorrectEnumFallbackMechanism where RawValue: Codable {
    
    init(from decoder: Decoder) throws {
        
        // Fetching the value specified inside the JSON
        let value = try decoder.singleValueContainer().decode(RawValue.self)
        
        // Creating the instance based on the provided value or falling back
        // to a default value in case the provided value for creating is
        // invalid
        self = Self(rawValue: value) ?? Self.fallbackForIncorrectEnumValue
    }
    
    /// Since `RawRepresentable` declares a `encode(coder:)` by default so we
    /// don't need to implement that explicitly
}

struct Person: Codable {
    let name: String
    let favouriteBeverage: Beverage
}

enum Beverage: String, IncorrectEnumFallbackMechanism {
    
    static var fallbackForIncorrectEnumValue: Beverage = .SomeWeirdBeverage
    
    case Tea
    case Coffee
    
    case SomeWeirdBeverage
}

let jsonString = """
{
    "name": "Shubham Bakshi",
    "favouriteBeverage": "Juice"
}
""".data(using: .utf8)!

let decodedJSON = try! JSONDecoder().decode(Person.self, from: jsonString)

switch decodedJSON.favouriteBeverage {
    
case .Tea:
    print("Ohhh I love some Tea")
    
case .Coffee:
    print("Coffee is no Tea but meh, I'll take it")
    
case .SomeWeirdBeverage:
    print("Wait! What is that?")
}

¡Eso es, amigos! ¡Feliz codificación!

Puedes conectarte conmigo en LinkedIn o puedes ponerte en contacto conmigo a través de otros canales