본문 바로가기

iOS/DataParsing

[iOS] Codable로 String타입의 JSON데이터-> Data타입으로 Decode. | String -> UIImage decode? #5

 최근에 AutoLayout과 MVVM을 공부하면서 Codable로 데이터 파싱하는 방법에 대해 알게 되었다. 그리고 내가 예전에 재은씨 실전편 공부하면서 만들었던 메모장 앱을 전부 리펙터링 하고 있다.

 

 아.. 미리 말하자면 String타입의 JSON 데이터를 Codable struce 프로퍼티 UIImage로 변환시도는 아직 성공하지 않아서 우선 String타입의 데이터를 Data로 변환하는 과정을 기록했다. + Container의 의미와 decode(_:forKey:)에 대해 조금 정리를 했다.(틀린 부분이 있을 수 있습니다. 조언 주시면 감사합니다.)

 

로그인할 때 아이디 패스워드에 따라 로그인 성공시 사용자의 정보를 파싱할 수 있는 REST api가 있다.

 

로그인 성공 시

{
	"user_info" : {
    	"name" : "홍길동",
        "account" : "abc@abc.com",
        "user_id" : 1,
        "profile_path" : "http://.../abc.png"
    },
    "result_code" = 0,
    "result" = "SUCCESS",
    "token_type" : "",
    "access_token" : "23423fef...",
    "refresh_token" : "2f223...",
    "error_msg" : "정상적으로..."
}

 

대략적으로 이런 형식으로 데이터를 받을수 있다.

 

여기서 난 snake_case 표기법은 사용하지 않을 것이고 가장 중요한 것은 파싱받은 profile_path 상의 이미지 String을 Codable타입의 Model 에 속해있는 UIImage 프로퍼티로 받고 싶었다. 하지만 Codable타입은 UIImage가 지원되지 않았기에 UIImage로 바꾸기 쉽게 Data타입으로 변환 받고 싶다는 생각이 들었다.

 

즉 String의 JSON형식의 데이터를 Data 타입으로 받고 싶다는 말이다!!!


 

 

struct UserInfoManagerLoginModel: Codable {
    let resultCode: Int
    let accessToken: String
    let refreshToken: String
    let userInfo: UserInfoModel
    let errorMessage: String
    
    enum CodingKeys: String, CodingKey {
        case resultCode = "result_code"
        case accessToken = "access_token"
        case refreshToken = "refresh_token"
        case userInfo = "user_info"
        case errorMessage = "error_msg"
    }
}

struct UserInfoModel: Codable {
    let loginID: Int
    let account: String
    let name: String
    let profileData: Data?
    
    enum CodingKeys: String, CodingKey {
        case loginID = "user_id"
        case account
        case name
        case profileData = "profile_path"
    }
}

 UIImage는 시도해봤지만 ,, 실패했다. 그래서 profile_path (String)값을 profileData (Data)로 저장할 것이다.

 

JSON을 Decode를 할 때 Decode로 받은 값을 각각의 UserInfoModel 프로퍼티에 저장하기 위해 init(from:) 함수가 사용된다. 이 함수를 구현할 때는 모든 프로퍼티들의 값을 전부 컨테이너를 통해 파싱된 값, forKey로 decode를 해야한다. 안그러면 디코딩이 되지 않아 에러가..... 발생한다.

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.loginID = try container.decode(Int.self, forKey: .loginID)
    self.account = try container.decode(String.self, forKey: .account)
    self.name = try container.decode(String.self, forKey: .name)
    self.profileData = try container.decodeIfPresent(Data.self, forKey: .profileData)
}

 

 

 

init(from:)을 커스텀했다.

 

대부분의 프로퍼티는 서버 값의 타입을 그대로 디코딩 하지만 profileData는 Data.self 값이 오는게 아니라 String.self값이 오게 됨으로 요부분을 좀 수정했다.

 

- decoder.container(keyedBy: CodingKeys.self)

 

위에서 CodingKey를 채택한 CodingKeys enum을 정의했기에 CodingKeys타입으로 입력된 디코더의 컨테이너 데이터를 반환. 즉 모든 CodingKey 값 데이터를 반환한다는 의미이다.

 

그리고 conatiner의 decode(_:forKey:) CodingKey값을 통해 내가 원하는 값으로 디코드 할 수 있다.

이를 활용해서 우선 .profileData의 JSON 파싱된 String값을 imagePath프로퍼티에 받아서 Data로 변환해준 후에 저장했다.

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    loginID = try container.decode(Int.self, forKey: .loginID)
    account = try container.decode(String.self, forKey: .account)
    name = try container.decode(String.self, forKey: .name)

    guard let imagePath = try? container.decode(String.self, forKey: .profileData),
        let imgURL = URL(string: imagePath),
        let imgData = try? Data(contentsOf: imgURL) else {
        profileData = nil
        return
    }

    profileData = imgData
}

UIImage로 바로 받아올 수 는 없었지만 우선 Data로 변환했기에 바로 UIImage로 적용할 수는 있었지만.. 뭔가찝찝하다.