Codable: 잘못된 Enum 값 처리

May 09 2023
코드베이스 보호: 예상치 못한 enum 값 처리 조사 결과 백엔드가 코드의 enum 내에 존재하지 않는 특정 enum 기반 필드에 대한 값을 제공한 것으로 밝혀져 앱이 갑자기 충돌하기 시작한 상황에 직면한 적이 있습니까? 다음 예를 생각해 보십시오. 아, 나는 Tea를 좋아합니다. 하지만 JSON 데이터의 favouriteBeverage를 Tea에서 Juice(열거형, Beverage 내에 존재하지 않는 값)로 변경하면 어떻게 될까요? "유효하지 않은 문자열 값 Juice에서 Beverage를 초기화할 수 없습니다."라는 메시지 부분에 초점을 맞춥니다. 놀이터 코드가 아닌 실제 앱이었다면 앱이 충돌했을 것입니다. 그렇다면 백엔드가 응답에 유효하지 않은 값을 보내더라도 앱이 완전히 충돌하는 대신 이를 처리할 수 있어야 한다는 사실을 어떻게 처리해야 할까요? 방법 중 하나이자 개인적으로 가장 좋아하는 방법은 다음과 같습니다.

코드베이스 보호: 예상치 못한 enum 값 처리

Unsplash에 있는 Kostiantyn Li의 사진

조사 결과 백엔드가 코드의 열거형 내부에 존재하지 않는 특정 열거형 기반 필드에 대한 값을 제공한 것으로 밝혀져 갑자기 앱이 충돌하기 시작한 상황에 직면한 적이 있습니까?

다음 예를 고려하십시오.

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

오 난 차를 좋아해

favouriteBeverage그러나 JSON 데이터를 Tea 에서 Juice ( 열거형 Beverage 내에 존재하지 않는 값 ) 로 변경하면 어떻게 될까요 ?

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

"유효하지 않은 문자열 값 주스에서 음료를 초기화할 수 없습니다"라는 메시지 부분에 초점을 맞춥니다.

플레이그라운드 코드가 아닌 실제 앱이었다면 앱이 다운되었을 것입니다. 그렇다면 백엔드가 응답에 유효하지 않은 값을 보내더라도 앱이 완전히 충돌하는 대신 이를 처리할 수 있어야 한다는 사실을 어떻게 처리해야 할까요 ?

방법 중 하나이자 개인적으로 가장 좋아하는 방법은 잘못된 열거형 폴백 메커니즘입니다.

그것은 메커니즘을 말하지만 모든 것을 처리하는 프로토콜에 가깝습니다. 자, 시작합니다…

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

기다리다! 저게 뭐에요?

백엔드가 우리에게 무엇을 보내든 상관없이 favouriteBeverage우리는 그것을 처리하거나 마실 수 있습니다.

이제 전체 코드는 다음과 같습니다.

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

그게 다야! 행복한 코딩!

LinkedIn 에서 저와 연결하거나 다른 채널 을 통해 저와 연락할 수 있습니다.