Codable : gestion des valeurs d'énumération incorrectes

May 09 2023
Sauvegarder votre base de code : traiter les valeurs d'énumération imprévues Avez-vous déjà été confronté à une situation où votre application a commencé à planter de nulle part, ce qui, après enquête, a révélé que le backend a fourni une valeur pour certains champs basés sur l'énumération qui n'existent pas dans l'énumération dans le code ? Prenons l'exemple suivant : Ohhh, j'aime du thé. Mais que se passe-t-il si nous modifions favouriteBeverage dans les données JSON de Tea à Juice (une valeur qui n'existe pas dans l'énumération, Beverage) ? Concentrez-vous sur la partie du message : "Impossible d'initialiser Beverage à partir d'une valeur de chaîne non valide Juice" S'il s'agissait d'une application réelle au lieu d'un code de terrain de jeu, l'application aurait planté. Alors, comment prenons-nous soin du fait que même si le backend envoie une valeur invalide dans la réponse, notre application devrait être capable de la gérer au lieu de planter carrément ? L'un des moyens, et mon préféré:

Sauvegarde de votre base de code : gestion des valeurs d'énumération imprévues

Photo de Kostiantyn Li sur Unsplash

Avez-vous déjà été confronté à une situation où votre application a commencé à planter de nulle part, ce qui, après enquête, indique que le backend a fourni une valeur pour un certain champ basé sur l'énumération qui n'existe pas à l'intérieur de l'énumération dans le code ?

Considérez l'exemple suivant :

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 j'aime le thé

Mais que se passe-t-il si nous changeons favouriteBeverageles données JSON de Tea en Juice (une valeur qui n'existe pas à l'intérieur de l'énumération, Beverage ) ?

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

Concentrez-vous sur la partie du message : "Impossible d'initialiser la boisson à partir d'un jus de valeur de chaîne non valide"

S'il s'agissait d'une application réelle au lieu d'un code de terrain de jeu, l'application aurait planté. Alors, comment prenons-nous soin du fait que même si le backend envoie une valeur invalide dans la réponse, notre application devrait être capable de la gérer au lieu de planter carrément ?

L'un des moyens, et mon préféré: Incorrect Enum Fallback Mechanism.

Ça dit mécanisme mais c'est plus ou moins un protocole qui s'occupe de tout. Alors, on y va….

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

Attendez! Qu'est-ce que c'est?

Peu importe ce que le backend nous envoie comme favouriteBeverage, nous pourrons le gérer, ou le boire, peu importe !

Donc, notre code complet ressemblera maintenant à ceci :

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

C'est tout, les amis ! Bon codage !

Vous pouvez vous connecter avec moi sur LinkedIn ou me contacter via d'autres canaux