Codable: gestione di valori di enumerazione errati

May 09 2023
Salvaguardia della tua base di codice: gestione di valori enum imprevisti Hai mai affrontato una situazione in cui la tua app ha iniziato a bloccarsi dal nulla, che dopo un'indagine ha rivelato che il back-end ha fornito un valore per un determinato campo basato su enum che non esiste all'interno dell'enumerazione nel codice? Considera il seguente esempio: Ohhh I love some Tea Ma cosa succede se cambiamo favoriteBeverage nei dati JSON da Tea a Juice(un valore che non esiste all'interno dell'enum, Beverage) ? Concentrati sulla parte del messaggio: "Impossibile inizializzare la bevanda da un valore di stringa non valido Juice" Se si trattava di un'app reale anziché di un codice di gioco, l'app si sarebbe arrestata in modo anomalo. Quindi, come ci prendiamo cura del fatto che anche se il backend invia un valore non valido nella risposta, la nostra app dovrebbe essere in grado di gestirlo invece di bloccarsi completamente? Uno dei modi, e il mio preferito:

Salvaguardia della base di codice: gestione di valori enum imprevisti

Foto di Kostiantyn Li su Unsplash

Hai mai affrontato una situazione in cui la tua app ha iniziato a bloccarsi dal nulla, il che, dopo un'indagine, ha rivelato che il back-end ha fornito un valore per un determinato campo basato sull'enumerazione che non esiste all'interno dell'enumerazione nel codice?

Considera il seguente esempio:

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 mi piace un po' di tè

Ma cosa succede se cambiamo favouriteBeveragei dati JSON da Tea a Juice (un valore che non esiste all'interno dell'enumerazione, Beverage )?

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

Concentrati sulla parte del messaggio: "Impossibile inizializzare la bevanda da un valore di stringa non valido Juice"

Se si trattasse di un'app reale invece di un codice del parco giochi, l'app si sarebbe arrestata in modo anomalo. Quindi, come ci prendiamo cura del fatto che anche se il back-end invia un valore non valido nella risposta, la nostra app dovrebbe essere in grado di gestirlo invece di andare in crash ?

Uno dei modi, e il mio preferito: Meccanismo di fallback Enum errato.

Dice meccanismo ma è più o meno un protocollo che si occupa di tutto. Quindi, ci siamo….

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

Aspettare! Che cos'è?

Non importa ciò che il backend ci invia come favouriteBeverage, saremo in grado di gestirlo o berlo, qualunque cosa!

Quindi il nostro codice completo sarà ora simile a questo:

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

Questo è tutto, gente! Buona codifica!

Puoi connetterti con me su LinkedIn oppure puoi metterti in contatto con me tramite altri canali