Swift/개념 & 응용

[Swift] Enum을 이용한 Mixed Type JSON 디코딩

유정주 2023. 5. 2. 23:01
반응형

서론

JSONArray의 아이템 타입이 혼합될 수 있습니다.

let mixedData = """
[{
    "type": "movie",
    "id": 100,
    "title": "타이타닉",
    "country": "USA"
},
{
    "type": "person",
    "id": 101,
    "name": "레오나르도 디카프리오",
    "role": "Actor"
},
{
    "type": "music",
    "id": 102,
    "title": "My Heart Will Go On",
    "artist": "Céline Dion"
}]
""".data(using: .utf8)!

위 JSON을 보면 각 아이템의 type, id는 공통되고 이외 key 구성이 다릅니다.

이럴 경우 type을 이용해 if문으로 처리할 수도 있지만, Enum을 이용하면 보다 Swift하게 파싱할 수 있습니다.

이번 포스팅에서는 이 방법에 대해 알아보겠습니다.

 

모델 정의

JSON에서 사용되는 모델을 Codable 객체로 정의합니다.

위 데이터에서는 세 개의 모델로 구성되어 있습니다.

struct Movie: Codable {
    let id: Int
    let title: String
    let country: String
}

struct Person: Codable {
    let id: Int
    let name: String
    let role: String
}

struct Music: Codable {
    let id: Int
    let title: String
    let artist: String
}

모델 이름을 type과 동일하게 맞춰주면 가독성이 향상됩니다.

 

Enum 정의

세 개의 모델을 포함하는 Enum을 정의합니다.

enum Content {
    case movie(Movie)
    case person(Person)
    case music(Music)
}

저는 Content라는 이름으로 정의했습니다.

위 Enum을 이용해 JSON을 파싱합니다.

 

JSON Decoding

가독성 향상을 위해 Extension으로 Codable을 준수합니다.

extension Content: Codable {
    private enum CodingKeys: String, CodingKey {
        case type = "type"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let singleContainer = try decoder.singleValueContainer()
        
        let type = try container.decode(String.self, forKey: .type)
        switch type {
        case "movie":
            let movie = try singleContainer.decode(Movie.self)
            self = .movie(movie)
        case "person":
            let person = try singleContainer.decode(Person.self)
            self = .person(person)
        case "music":
            let music = try singleContainer.decode(Music.self)
            self = .music(music)
        default:
            fatalError("Unknown type of content.")
        }
    }
}

Content를 확장해서 Codable을 채택했습니다.

파싱할 key인 type을 CodingKeys에 정의해주었고, init(from decoder: Decoder)을 구현합니다.

init(from decoder: Decoder)에서 type에 따라 Content를 생성합니다.

 

테스트

마지막입니다.

구현한 Enum을 이용해 JSONDecoder 객체로 JSON 데이터를 파싱해봅시다.

let decoder = JSONDecoder()
let content = try decoder.decode([Content].self, from: mixedData)
print(content)
//Movie(id: 100, title: "타이타닉", country: "USA")
//Person(id: 101, name: "레오나르도 디카프리오", role: "Actor")
//Music(id: 102, title: "My Heart Will Go On", artist: "Céline Dion")

각 타입에 맞게 제대로 파싱된 것을 볼 수 있습니다.

 

전체 코드

import UIKit

let mixedData = """
[{
    "type": "movie",
    "id": 100,
    "title": "타이타닉",
    "country": "USA"
},
{
    "type": "person",
    "id": 101,
    "name": "레오나르도 디카프리오",
    "role": "Actor"
},
{
    "type": "music",
    "id": 102,
    "title": "My Heart Will Go On",
    "artist": "Céline Dion"
}]
""".data(using: .utf8)!

enum Content {
    case movie(Movie)
    case person(Person)
    case music(Music)
}

struct Movie: Codable {
    let id: Int
    let title: String
    let country: String
}

struct Person: Codable {
    let id: Int
    let name: String
    let role: String
}

struct Music: Codable {
    let id: Int
    let title: String
    let artist: String
}

extension Content: Codable {
    private enum CodingKeys: String, CodingKey {
        case type = "type"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let singleContainer = try decoder.singleValueContainer()
        
        let type = try container.decode(String.self, forKey: .type)
        switch type {
        case "movie":
            let movie = try singleContainer.decode(Movie.self)
            self = .movie(movie)
        case "person":
            let person = try singleContainer.decode(Person.self)
            self = .person(person)
        case "music":
            let music = try singleContainer.decode(Music.self)
            self = .music(music)
        default:
            fatalError("Unknown type of content.")
        }
    }
}

let decoder = JSONDecoder()
let content = try decoder.decode([Content].self, from: mixedData)
print(content)

 

참고

https://david.y4ng.fr/codable-with-mixed-types-of-data/


아직은 초보 개발자입니다.

더 효율적인 코드 훈수 환영합니다!

공감 댓글 부탁드립니다.

 

 

반응형