지금까지는 swiftJSON / Alamofire을 사용하여 API에서 받아온 JSON 정보를 처리했다. 하지만 swiftyJSON을 사용하게 되면, 나중에 많은 데이터들을 처리할 때 처리 시간이 늘어나게 되는 단점이 있다. 이를 개선한 방법이 Codable이다.
Codable은 decodable 과 encodable 두 가지로 구성되어 있다. encoding은 구조체에서 JSON 형식으로 변환되는 것을 의미하고, decoding은 JSON에서 Struct로 가는 것을 의미한다.
이것이 사용되는 형식을 알아보자.
let json = """
{
"quote": "Count your age by friend, not years. Count your life by smiles, not tears.",
"author": "John Lennon"
}
"""
struct Quote: Decodable {
let quote: String
let author: String
}
//String -> Data
guard let result = json.data(using: .utf8) else { fatalError("ERROR") }
print(result)
//Data -> Quote
do {
let value = try JSONDecoder().decode(Quote.self, from: result)
print(value)
print(value.quote)
print(value.author)
} catch {
print(error)
}
json 데이터 값을 decodable을 이용하여 Quote라는 구조체에 저장하고, 이를 활용하는 형태로 변환해주었다.
print로 찍힌 값을 나타내면
Quote(quote: "Count your age by friend, not years. Count your life by smiles, not tears.", author: "John Lennon")
Count your age by friend, not years. Count your life by smiles, not tears.
John Lennon
위와 같이 json의 값이 변환되어 값이 잘 나타나는것을 볼 수 있다.
//서버 응답값을 좀 변형해서 모델로 넣고 싶은 경우
let json = """
{
"quote_content": "Count your age by friend, not years. Count your life by smiles, not tears.",
"author_name": null,
"likeCount": 12345
}
"""
struct Quote: Decodable {
let ment: String
let author: String
let like: Int
let isInfluencer: Bool // 10000개 이상 좋아요 받은 경우
enum CodingKeys: String, CodingKey { // 내부적으로 선언되어 있는 열거형
case ment = "quote_content"
case author = "author_name"
case like = "likeCount"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
ment = try container.decode(String.self, forKey: .ment)
author = try container.decodeIfPresent(String.self, forKey: .author) ?? "unknown"
like = try container.decode(Int.self, forKey: .like)
isInfluencer = (10000...).contains(like) ? true : false
}
}
//String -> Data
guard let result = json.data(using: .utf8) else { fatalError("ERROR") }
print(result)
//Data -> Quote
do {
let value = try JSONDecoder().decode(Quote.self, from: result)
print(value)
print(value.ment)
print(value.author)
} catch {
print(error)
}
위의 형태를 보면, json에서는 isInfluencer에 대한 정보를 제공하지 않았지만, 우리가 struct에서 직접 init을 초기화하고, 열거형으로 CodingKey를 사용함으로써 우리가 원하는 모델을 추가하고 활용할 수 있게 되었다.
print로 값이 잘 나타나는지 확인해보면
Quote(ment: "Count your age by friend, not years. Count your life by smiles, not tears.", author: "unknown", like: 12345, isInfluencer: true)
Count your age by friend, not years. Count your life by smiles, not tears.
unknown
이렇게 isInfluencer에 대한 값도 잘 나타나는 것을 볼 수 있다.
하지만 이렇게 사용하다보면, 우리는 직접 json의 형태에 따라 struct를 구현해주어야 하는 불편함이 있다. 이를 더욱 편리하게 해주는 사이트가 있는데, 바로 https://app.quicktype.io/ 이다.
이 사이트는 json의 값을 넣어주면 알아서 struct를 구성해주는 편리한 기능을 제공한다.
{
"totSellamnt": 104899923000,
"returnValue": "success",
"drwNoDate": "2022-08-27",
"firstWinamnt": 1276406507,
"drwtNo6": 29,
"drwtNo4": 17,
"firstPrzwnerCo": 19,
"drwtNo5": 24,
"bnusNo": 9,
"firstAccumamnt": 24251723633,
"drwNo": 1030,
"drwtNo2": 5,
"drwtNo3": 11,
"drwtNo1": 2
}
위와 같은 json 데이터를 quicktype에 넣어주면,
이렇게 자동적으로 struct를 구현해준다!!!
(감사합니다.. 감사합니다...)
이를 이용해 json 값을 확인해 보는 코드를 작성해보면,
import Foundation
let json = """
{
"totSellamnt": 104899923000,
"returnValue": "success",
"drwNoDate": "2022-08-27",
"firstWinamnt": 1276406507,
"drwtNo6": 29,
"drwtNo4": 17,
"firstPrzwnerCo": 19,
"drwtNo5": 24,
"bnusNo": 9,
"firstAccumamnt": 24251723633,
"drwNo": 1030,
"drwtNo2": 5,
"drwtNo3": 11,
"drwtNo1": 2
}
"""
//quicktype
struct Lotto: Codable {
let totSellamnt: Int
let returnValue, drwNoDate: String
let firstWinamnt, drwtNo6, drwtNo4, firstPrzwnerCo: Int
let drwtNo5, bnusNo, firstAccumamnt, drwNo: Int
let drwtNo2, drwtNo3, drwtNo1: Int
}
guard let result = json.data(using: .utf8) else { fatalError("ERROR") }
print(result)
//Data -> Quote
do {
let value = try JSONDecoder().decode(Lotto.self, from: result)
print(value)
} catch {
print(error)
}
우리는 이렇게 값을 처리하는 부분만 작성해주면 된다.
print부분
Lotto(totSellamnt: 104899923000, returnValue: "success", drwNoDate: "2022-08-27", firstWinamnt: 1276406507, drwtNo6: 29, drwtNo4: 17, firstPrzwnerCo: 19, drwtNo5: 24, bnusNo: 9, firstAccumamnt: 24251723633, drwNo: 1030, drwtNo2: 5, drwtNo3: 11, drwtNo1: 2)
그러면 우리는 quicktype에서 제공하는 구조체 그대로 사용해야 하나요? 에 대한 답변으로는 아니요이다!
let json = """
{
"boxOfficeResult": {
"boxofficeType": "일별 박스오피스",
"showRange": "20120101~20120101",
"dailyBoxOfficeList": [
{
"rnum": "1",
"rank": "1",
"rankInten": "0",
"rankOldAndNew": "OLD",
"movieCd": "20112207",
"movieNm": "미션임파서블:고스트프로토콜",
"openDt": "2011-12-15",
"salesAmt": "2776060500",
"salesShare": "36.3",
"salesInten": "-415699000",
"salesChange": "-13",
"salesAcc": "40541108500",
"audiCnt": "353274",
"audiInten": "-60106",
"audiChange": "-14.5",
"audiAcc": "5328435",
"scrnCnt": "697",
"showCnt": "3223"
},
{
"rnum": "2",
"rank": "2",
"rankInten": "1",
"rankOldAndNew": "OLD",
"movieCd": "20110295",
"movieNm": "마이 웨이",
"openDt": "2011-12-21",
"salesAmt": "1189058500",
"salesShare": "15.6",
"salesInten": "-105894500",
"salesChange": "-8.2",
"salesAcc": "13002897500",
"audiCnt": "153501",
"audiInten": "-16465",
"audiChange": "-9.7",
"audiAcc": "1739543",
"scrnCnt": "588",
"showCnt": "2321"
},
{
"rnum": "3",
"rank": "3",
"rankInten": "-1",
"rankOldAndNew": "OLD",
"movieCd": "20112621",
"movieNm": "셜록홈즈 : 그림자 게임",
"openDt": "2011-12-21",
"salesAmt": "1176022500",
"salesShare": "15.4",
"salesInten": "-210328500",
"salesChange": "-15.2",
"salesAcc": "10678327500",
"audiCnt": "153004",
"audiInten": "-31283",
"audiChange": "-17",
"audiAcc": "1442861",
"scrnCnt": "360",
"showCnt": "1832"
},
{
"rnum": "4",
"rank": "4",
"rankInten": "0",
"rankOldAndNew": "OLD",
"movieCd": "20113260",
"movieNm": "퍼펙트 게임",
"openDt": "2011-12-21",
"salesAmt": "644532000",
"salesShare": "8.4",
"salesInten": "-75116500",
"salesChange": "-10.4",
"salesAcc": "6640940000",
"audiCnt": "83644",
"audiInten": "-12225",
"audiChange": "-12.8",
"audiAcc": "895416",
"scrnCnt": "396",
"showCnt": "1364"
},
{
"rnum": "5",
"rank": "5",
"rankInten": "0",
"rankOldAndNew": "OLD",
"movieCd": "20113271",
"movieNm": "프렌즈: 몬스터섬의비밀 ",
"openDt": "2011-12-29",
"salesAmt": "436753500",
"salesShare": "5.7",
"salesInten": "-89051000",
"salesChange": "-16.9",
"salesAcc": "1523037000",
"audiCnt": "55092",
"audiInten": "-15568",
"audiChange": "-22",
"audiAcc": "202909",
"scrnCnt": "290",
"showCnt": "838"
},
{
"rnum": "6",
"rank": "6",
"rankInten": "1",
"rankOldAndNew": "OLD",
"movieCd": "19940256",
"movieNm": "라이온 킹",
"openDt": "1994-07-02",
"salesAmt": "507115500",
"salesShare": "6.6",
"salesInten": "-114593500",
"salesChange": "-18.4",
"salesAcc": "1841625000",
"audiCnt": "45750",
"audiInten": "-11699",
"audiChange": "-20.4",
"audiAcc": "171285",
"scrnCnt": "244",
"showCnt": "895"
},
{
"rnum": "7",
"rank": "7",
"rankInten": "-1",
"rankOldAndNew": "OLD",
"movieCd": "20113381",
"movieNm": "오싹한 연애",
"openDt": "2011-12-01",
"salesAmt": "344871000",
"salesShare": "4.5",
"salesInten": "-107005500",
"salesChange": "-23.7",
"salesAcc": "20634684500",
"audiCnt": "45062",
"audiInten": "-15926",
"audiChange": "-26.1",
"audiAcc": "2823060",
"scrnCnt": "243",
"showCnt": "839"
},
{
"rnum": "8",
"rank": "8",
"rankInten": "0",
"rankOldAndNew": "OLD",
"movieCd": "20112709",
"movieNm": "극장판 포켓몬스터 베스트 위시「비크티니와 백의 영웅 레시라무」",
"openDt": "2011-12-22",
"salesAmt": "167809500",
"salesShare": "2.2",
"salesInten": "-45900500",
"salesChange": "-21.5",
"salesAcc": "1897120000",
"audiCnt": "24202",
"audiInten": "-7756",
"audiChange": "-24.3",
"audiAcc": "285959",
"scrnCnt": "186",
"showCnt": "348"
},
{
"rnum": "9",
"rank": "9",
"rankInten": "0",
"rankOldAndNew": "OLD",
"movieCd": "20113311",
"movieNm": "앨빈과 슈퍼밴드3",
"openDt": "2011-12-15",
"salesAmt": "137030000",
"salesShare": "1.8",
"salesInten": "-35408000",
"salesChange": "-20.5",
"salesAcc": "3416675000",
"audiCnt": "19729",
"audiInten": "-6461",
"audiChange": "-24.7",
"audiAcc": "516289",
"scrnCnt": "169",
"showCnt": "359"
},
{
"rnum": "10",
"rank": "10",
"rankInten": "0",
"rankOldAndNew": "OLD",
"movieCd": "20112708",
"movieNm": "극장판 포켓몬스터 베스트 위시 「비크티니와 흑의 영웅 제크로무」",
"openDt": "2011-12-22",
"salesAmt": "125535500",
"salesShare": "1.6",
"salesInten": "-40756000",
"salesChange": "-24.5",
"salesAcc": "1595695000",
"audiCnt": "17817",
"audiInten": "-6554",
"audiChange": "-26.9",
"audiAcc": "235070",
"scrnCnt": "175",
"showCnt": "291"
}
]
}
}
"""
위와같은 복잡한 구조와 양의 JSON 데이터가 있다고 해보자.
위의 json을 quicktype을 사용하여 구조체를 만들면 이와 같이 나타난다.
하지만 앱을 만들때 위에서 선언한 모든 구조체의 요소들을 사용하는 것은 아니다. 우리가 필요한 것만 선언해서 사용하면 되기 때문에, 위의 struct를 그대로 사용할 필요는 없다.
let json = """
{
영화 json 데이터
}
"""
// MARK: - BoxOffice
struct BoxOffice: Codable {
let boxOfficeResult: BoxOfficeResult
}
// MARK: - BoxOfficeResult
struct BoxOfficeResult: Codable {
let boxofficeType, showRange: String
let dailyBoxOfficeList: [DailyBoxOfficeList]
}
// MARK: - DailyBoxOfficeList
struct DailyBoxOfficeList: Codable {
let rnum, rank, rankInten: String
let movieCd, movieNm, openDt, salesAmt: String
}
guard let result = json.data(using: .utf8) else { fatalError("ERROR") }
print(result)
//Data -> Quote
do {
let value = try JSONDecoder().decode(BoxOffice.self, from: result)
print(value)
} catch {
print(error)
}
위의 코드처럼, quicktype에서 받아온 struct에서 사용하지 않는 요소들은 지워서 활용해도 된다.