Codable: Menangani Nilai Enum yang Salah

May 09 2023
Melindungi basis kode Anda: berurusan dengan nilai enum yang tidak diantisipasi Pernah menghadapi situasi di mana aplikasi Anda mulai mogok entah dari mana, yang setelah diselidiki menghasilkan bahwa backend memberikan nilai untuk bidang berbasis enum tertentu yang tidak ada di dalam enum dalam kode? Pertimbangkan contoh berikut: Ohhh saya suka Teh Tapi bagaimana jika kita mengubah minuman favorit di data JSON dari Teh menjadi Jus (nilai yang tidak ada di dalam enum, Minuman)? Fokus pada bagian pesan: “Cannot initialize Beverage from invalid String value Juice” Jika itu adalah aplikasi yang sebenarnya dan bukan kode taman bermain, aplikasi tersebut akan macet. Jadi, bagaimana kita menjaga fakta bahwa meskipun backend mengirimkan nilai yang tidak valid dalam respons, aplikasi kita seharusnya dapat menanganinya alih-alih langsung mogok? Salah satu cara, dan favorit pribadi saya:

Menjaga basis kode Anda: berurusan dengan nilai enum yang tidak diantisipasi

Foto oleh Kostiantyn Li di Unsplash

Pernah menghadapi situasi di mana aplikasi Anda mulai mogok entah dari mana, yang setelah diselidiki menghasilkan bahwa backend memberikan nilai untuk bidang berbasis enum tertentu yang tidak ada di dalam enum dalam kode?

Pertimbangkan contoh berikut:

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 aku suka Teh

Tetapi bagaimana jika kita mengubah favouriteBeveragedata JSON dari Tea menjadi Juice (nilai yang tidak ada di dalam enum, Beverage )?

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

Fokus pada bagian pesan: “Cannot initialize Beverage from invalid String value Juice”

Jika itu adalah aplikasi yang sebenarnya dan bukan kode taman bermain, aplikasi tersebut akan macet. Jadi, bagaimana kita menjaga fakta bahwa meskipun backend mengirimkan nilai yang tidak valid dalam respons, aplikasi kita harus dapat menanganinya alih-alih langsung mogok ?

Salah satu cara, dan favorit pribadi saya: Mekanisme Fallback Enum Salah.

Dikatakan mekanisme tetapi kurang lebih merupakan protokol yang menangani semuanya. Jadi, ini dia….

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

Tunggu! Apa itu?

Apa pun yang dikirimkan backend kepada kami favouriteBeverage, kami akan dapat menanganinya, atau meminumnya, apa pun!

Jadi kode lengkap kita sekarang akan terlihat seperti ini:

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

Itu saja, teman-teman! Selamat Coding!

Anda dapat terhubung dengan saya di LinkedIn atau dapat menghubungi saya melalui saluran lain