개발/Swift

[Swift] 다양한 데이터 타입 동적으로 Decoding 하기

덤벨로퍼 2023. 1. 9. 17:55

위 데이터 처럼 하나의 리스트에 다른 타입의 데이터를 받아오는 경우가 있다.

이때 해당 데이터를 디코딩할때 문제를 겪게된다.

 

 

해결 할수 있는 방법은 위와 같이 모든 프로퍼티들을 옵셔널하게 주는 방법이 있다.

그러면 디코딩 한 결과는 옵셔널 하며 데이터 혹은 nil 이 들어갈것이다.

이런 경우 문제점은 프로퍼티가 모두 옵셔널 하기 때문에 언래핑을 꼭 해줘야 한다는것이다.

더 좋은 방식은 해당 타입을 나누어 다른 타입으로 구현해내는 것이다.

어느 방식으로든 분기를 태워서 A타입, B타입으로 나누어 필요한 프로퍼티만 설정해주는것이다.

enum CellItem: Decodable , Hashable{
    case company(Company)
    case horizontal(Horizontal)
    case none
    
    enum CodingKeys: String, CodingKey {
        case type = "cell_type"
    }
    
    enum CellType: String, Codable {
          case company = "CELL_TYPE_COMPANY"
          case horizontal = "CELL_TYPE_HORIZONTAL_THEME"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        guard let type = try? container.decode(CellType.self, forKey: .type) else {
            self = .none
            return
        }
        
        switch type {
           case .company:
               self = try .company(Company(from: decoder))
           case .horizontal:
               self = try .horizontal(Horizontal(from: decoder))
            
           }
    }
}

그러기 위해서 우선 Enum 타입으로 정의했다. company , horizontal 타입두개를 정의했고

나머지는 none으로 처리할것이다.

Hashable은 diffabledatasource를 사용하기 위함이니 무시하자

init()함수 를 보자

우선 타입을 String 값을 보고 CellType이라는 enum타입으로 변환해준다.

switch 문을 보면 여기서 분기를 타서 enum value값을 넣어주었음을 볼수 있다.

이제 Company 타입과 Horizontal 이라는 Struct를 구현해줘야한다.

해당 타입들은 Enum 안에서 정의했다.

//CellItem

struct Company: Decodable, Hashable{
        var cellType: CellType
        var name: String
        let logoPath: String
        let industryName: String
        let rateTotalAvg: Float
        let reviewSummary: String
        let interviewQuestion: String
        let salaryAvg: Int
        let updateDate: String
        
        private enum CodingKeys: String, CodingKey {
            case cellType = "cell_type"
           case logoPath = "logo_path"
           case name
           case industryName = "industry_name"
           case rateTotalAvg = "rate_total_avg"
           case reviewSummary = "review_summary"
           case interviewQuestion = "interview_question"
           case salaryAvg = "salary_avg"
           case updateDate = "update_date"
       }
        
        public init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            cellType = try container.decode(CellType.self, forKey: .cellType)

            logoPath = try container.decode(String.self, forKey: .logoPath)
            name = try container.decode(String.self, forKey: .name)
            industryName = try container.decode(String.self, forKey: .industryName)
            rateTotalAvg = try container.decode(Float.self, forKey: .rateTotalAvg)
            reviewSummary = try container.decode(String.self, forKey: .reviewSummary)
            interviewQuestion = try container.decode(String.self, forKey: .interviewQuestion)
            salaryAvg = try container.decode(Int.self, forKey: .salaryAvg)
            updateDate = try container.decode(String.self, forKey: .updateDate)
        }
        
    }
    struct Horizontal: Decodable , Hashable{
        
        var cellType: CellType
        var sectionTitle: String
        let count: Int
        var recommendRecruit: [RecruitItem]
        
        private enum CodingKeys: String, CodingKey {
            case cellType = "cell_type"
           case count
           case sectionTitle = "section_title"
           case recommendRecruit = "recommend_recruit"
       }
        
        public init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)

            cellType = try container.decode(CellType.self, forKey: .cellType)

            count = try container.decode(Int.self, forKey: .count)
            sectionTitle = try container.decode(String.self, forKey: .sectionTitle)
            recommendRecruit = try container.decode([RecruitItem].self, forKey: .recommendRecruit)
        }
    }

각 타입에는 공통 프로퍼티인 cellType과 그외 각자가 가져야할 프로퍼티를 넣어줬다.

이러면 이제 어느 타입의 데이터가오든 CellItem이라는 데이터 리스트로 디코딩이 가능해진다.

그리고 CellItem의 타입이 company 혹은 horizontal 로 나뉠것이며 그 안에 value 가 담겨져있다.

private func filterData(items: [CellItem], searchText: String) -> [CellItem] {
        let filtered = items.compactMap { item -> CellItem? in
        switch item{
            case .company(let companyItem):
                 let name = companyItem.name.lowercased()
                if name.contains(searchText) {
                    return item
                }
                return nil
            case .horizontal(let horizontalItem):
                var temp = horizontalItem
                temp.filterRecommendRecruit(text: searchText)
                 let recommendRecruit = temp.recommendRecruit
                if recommendRecruit.isEmpty { return nil }
                let cellItem = CellItem.horizontal(temp)
                return cellItem
                
            default:
                return nil
            }
        }
        return filtered
    }

위 함수는 받아온 데이터에서 검색어가 포함된 데이터만 필터링하는 함수이다.

다른거 무시하고 switch 문을 보면 enum 타입을 활용해 분기를 타고 value값을 가져올수 있음을 볼수 있다.

이렇게 사용하면 된다.