안녕하십니까 후르륵짭짭 입니다.
이번에는 Codable에서 ANY 타입을 파싱하는 방법에 대해 알아보려고 합니다.
이번에 과제를 하면서 Rest API로 내려오는 데이터가 "String"과 "INT"형으로 둘다 내려온다면
ANY 타입으로 받으려고 했는데,,,, 오류가 뜨는 겁니다...
그래서 해결 방법을 찾게 됐습니다..
** 해결 방법 **
이런 경우는 자주 없을 것이라 생각하지만, JSON으로 내려오는 데이터가 여러가지의 타입을 가지게 된다면
여러가지 타입을 가질 수 있도록 만들어줘야합니다.
일단 어느 부분에서 오류가 생기는지 확인 할 수 있는 방법에 대해 설명하겠습니다.
- 오류확인 위치 확인하는 방법 -
//Data Model
struct ItemModel : Codable {
let name : String?
let url : String?
let shop : String?
let start_datetime : String?
let end_datetime : String?
let price : Int?
let image_list : [String]?
}
데이터 모델은 이렇게 위에 처럼 구성이 되어 있고
//Input Json Data
let jsonReq = """
{
"id": -1,
"type": "item",
"start_datetime": -1,
"name": "",
"shop": "cjmallplus",
"order": 12,
"url": "",
"image": "",
"price": 0,
"live": null
}
"""
입력으로 보내지는 JSON 데이터는 위와 같이 생겼다고 합시다.
그러면 위의 내용을 보면 Start_dateTime은 Int형으로 입력으로 주어지지만,
Model은 String 타입으로 되어 있습니다.
그런데 위와 같이 파싱을 하게 된다면 오류가 발생하게 되는데,
//Parsing Code
func main(){
let data = jsonReq.data(using: .utf8)
do {
let decode = try JSONDecoder().decode(ItemModel.self, from: data!)
print(decode)
}
catch DecodingError.keyNotFound(let key, let context){
print("could not find key \(key) in JSON: \(context.debugDescription)")
}
catch DecodingError.valueNotFound(let key, let context){
print("could not find key \(key) in JSON: \(context.debugDescription)")
}
catch DecodingError.typeMismatch(let type, let context) {
print("type mismatch for type \(type) in JSON: \(context.debugDescription)")
} catch DecodingError.dataCorrupted(let context) {
print("data found to be corrupted in JSON: \(context.debugDescription)")
}
catch let jsonError{
print(jsonError.localizedDescription)
}
}
위와 같이 코드를 적어준다면 어느 부분에서 오류가 발생하는지 쉽게 알 수 있습니다.
결과로는 "type mismatch for type String in JSON: Expected to decode String but found a number instead." 이렇게
결과가 나왔습니다.
타입이 잘 못 됐다고 알려주는군요,,,,,
** ANY 타입 문제 해결 **
타입이 여려가지가 될 수 있는 상활일 때는 두가지 방법이 있습니다.
하나는 Init(decoder:)로 해결 하는 방법과 enum으로 해결하는 방법이 있습니다.
- Init(decoder:)로 해결하는 방법
init(decoder:)의 내용을 본다면 주어진 디코더로 새로운 객체를 디코딩하여 생성하는 것이라고 되어 있고
논의하기에, 이 생정자는 만약에 decode하는 과정 중 문제가 생긴다면 에러를 반환한다고 되어 있습니다.
예 맞습니다.
이렇게 하나씩 decode를 해줘야 문제를 해결 할 수 있습니다.
struct ItemModel : Codable {
let name : String?
let url : String?
let shop : String?
let start_datetime : String?
let end_datetime : String?
let price : Int?
let image_list : [String]?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String?.self, forKey: .name)
url = try container.decode(String?.self, forKey: .url)
shop = try container.decode(String?.self, forKey: .shop)
price = try container.decode(Int?.self, forKey: .price)
if let endDateTIme = try? container.decode(String?.self, forKey: .end_datetime) {
end_datetime = endDateTIme
}
else{
end_datetime = nil
}
if let imagelist = try? container.decode([String]?.self, forKey: .image_list) {
image_list = imagelist
}
else{
image_list = nil
}
if let dateTime = try? container.decode(String.self, forKey: .start_datetime){
start_datetime = dateTime
}
else{
let intType = try container.decode(Int.self, forKey: .start_datetime)
start_datetime = String(intType)
}
}
}
//결과
ItemModel(name: Optional(""), url: Optional(""), shop: Optional("cjmallplus"), start_datetime: Optional("-1"), end_datetime: nil, price: Optional(0), image_list: nil)
위와 같이 코드를 작성하는 방법이 있습니다.
하나씩 확인을 해보면
let container = try decoder.container(keyedBy: CodingKeys.self)
decoder.container는 현재 Codable 구조체에 해당하는 모든 Key 값들을 가져오게 됩니다.
그래서 Returns the data stored in this decoder 라고 적혀 있습니다.
그리고 우리는 각각의 Key 값들 모두!!!! 다!!!! 아래 처럼 적어줘야합니다.
name = try container.decode(String?.self, forKey: .name)
이렇게 하면 현재 name Key를 String 옵셔널 타입으로 디코딩 하겠다는 것을 의미합니다.
하지만 만약 JSON에 Key가 존재 하지 않을 수도 있고 타입도 다를 수가 있을 겁니다.
만약 JSON에 Key가 존재 하지 않는다면
if let endDateTIme = try? container.decode(String?.self, forKey: .end_datetime) {
end_datetime = endDateTIme
}
else{
end_datetime = nil
}
위와 같이 옵셔널 바인딩으로 해결을 해주시면 됩니다.
타입이 다른 경우에서도 위와 동일한 방식으로 아래 처럼 해주면 됩니다.
if let dateTime = try? container.decode(String.self, forKey: .start_datetime){
start_datetime = dateTime
}
else{
let intType = try container.decode(Int.self, forKey: .start_datetime)
start_datetime = String(intType)
}
또 다른 방식으로는 DO - Catch 문을 이용해서 해결하면 됩니다.
do{
start_datetime = try container.decode(String.self, forKey: .start_datetime)
}
catch{
let intType = try container.decode(Int.self, forKey: .start_datetime)
start_datetime = String(intType)
}
- Enum 방식으로 해결하는 방법
enum DynamicJsonProperty : Codable{
case string(String)
case int(Int)
init(from decoder : Decoder) throws {
let container = try decoder.singleValueContainer()
//Decode the property
do {
let stringValue = try container.decode(String.self)
self = .string(stringValue)
}
catch DecodingError.typeMismatch{
let intValue = try container.decode(Int.self)
self = .int(intValue)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let value):
try container.encode(value)
case .int(let value):
try container.encode(value)
}
}
}
위의 방식과 다른 것은 별로 없습니다.
하지만 가장 큰 차이는 CodingKey를 사용하냐? 아니면 singleValueContainer를 사용하냐 차이라고 생각합니다.
위에서 decode.container 방식은 모든 Key Type을 제공하는 방면에 enum은 하나의 Key에 적용하기 때문에
오직 Single Value를 제공하게 됩니다.
그리고 이 Single Value를 아래 코드로 분류 해줍니다.
//Decode the property
do {
let stringValue = try container.decode(String.self)
self = .string(stringValue)
}
catch DecodingError.typeMismatch{
let intValue = try container.decode(Int.self)
self = .int(intValue)
}
만약 우리가 원하는 String 값으로 해독이 된다면 String Case로 값을 넣어주고 그게 아니라
DecodingError에 TypeMismatch가 발생한다면 Int 값으로 넣어주도록 합니다.
이렇게 하면 우리가 원하는 방식대로 해결하게 됩니다.
하지만 Enum으로 할 경우에는 Decode 뿐만 아니라 Encode 까지 해줘야합니다.
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let value):
try container.encode(value)
case .int(let value):
try container.encode(value)
}
}
이것은 container에 값을 인코딩 해서 넣어주게 되는 방식 입니다.
그리고 마지막에 데이터 파싱을 할때
func main(){
let data = jsonReq.data(using: .utf8)
do {
let decode = try JSONDecoder().decode(ItemModel.self, from: data!)
print(decode)
switch decode.start_datetime {
case .int(let value):
print("Int Type : \(value)")
case .string(let value):
print("String Type : \(value)")
default :
break
}
}
catch DecodingError.keyNotFound(let key, let context){
print("could not find key \(key) in JSON: \(context.debugDescription)")
}
catch DecodingError.valueNotFound(let key, let context){
print("could not find key \(key) in JSON: \(context.debugDescription)")
}
catch DecodingError.typeMismatch(let type, let context) {
print("type mismatch for type \(type) in JSON: \(context.debugDescription)")
} catch DecodingError.dataCorrupted(let context) {
print("data found to be corrupted in JSON: \(context.debugDescription)")
}
catch let jsonError{
print(jsonError.localizedDescription)
}
}
이렇게 switch 문으로 Case 별로 나눠서 값을 가지게 할 수 있습니다.
지금까지 ANY 타입 형태로 값을 Codable로 해독하는 방법에 대해 공부했습니다.
내용이 길어서 쫌 힘들었네요
모두 즐코하세요
소스코드 :
참고 사이트 :
codable에서 생기는 오류 찾는 방법 :
www.hackingwithswift.com/forums/swiftui/decoding-json-data/3024
ANY 타입으로 된 프로퍼티를 Codable로 파싱하는 방법 :
stackoverflow.com/questions/48297263/how-to-use-any-in-codable-type
Any 타입으로 된 프로퍼티를 Enum으로 해결하는 방법 :
stackoverflow.com/a/46761102/13065642
Swift Codable의 Decode 방법 :
stackoverflow.com/a/48069236/13065642
String을 Data 타입으로 변경하는 방법 :
www.objc.io/blog/2018/02/13/string-to-data-and-back/
Enum 값을 Switch 문으로 처리하능 방법 :
'Xcode > Swift - PlayGround' 카테고리의 다른 글
PlayGround) 참조값으로 받을 수 있는 함수와 클로저의 놀라운 기능! (0) | 2021.01.14 |
---|---|
PlayGround ) 내가 공부한 CoreData의 처음 것 (0) | 2020.12.27 |
PlayGround) DateFormatter를 이용해서 날짜를 변경하자 (2) | 2020.12.18 |
PlayGround ) Closure의 기능은 무엇인가? (0) | 2020.12.08 |
PlayGround) Notification을 이용해서 데이터를 전송하자! (0) | 2020.11.16 |
댓글