반응형
서론
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/
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.
반응형