관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] json, codable, nestedContainer(keyedBy:forKey:), Decoding 중첩 모델 처리 방법 (디코딩) 본문

iOS 응용 (swift)

[iOS - swift] json, codable, nestedContainer(keyedBy:forKey:), Decoding 중첩 모델 처리 방법 (디코딩)

jake-kim 2022. 2. 7. 23:49

* 기본적인 Codable, Decode, Encode 개념은 이곳 참고

* 관련 포스팅 글

 

중첩 모델을 flatten 모델로 정의 방법

  • nestedContainer(keyedBy:forKey:) 사용
    • nestedContainer에 접근할때 사용되는 메소드

https://developer.apple.com/documentation/swift/keyeddecodingcontainer/2893204-nestedcontainer

ex) 아래 json 데이터에서 blog_info.name.first 값만 사용하고 싶은 경우, flat하게 모델 정의 방법?

{
  "user_id": "jake",
  "blog_info": {
    "name": {
      "first": "iOS 앱 개발 알아가기",
      "second": "SwiftUI 앱 개발 알아가기"
    }
  }
}
nestedContainer 사용 x - 중첩 nestedContainer 사용 o - flatten

구현 방법

- 아래 blog_info.name.first를 nested하지 않고 바로 myModel.blogName으로 접근하는 방법 구현

{
  "user_id": "jake",
  "blog_info": {
    "name": {
      "first": "iOS 앱 개발 알아가기",
      "second": "SwiftUI 앱 개발 알아가기"
    }
  }
}
  • nested된 json key값들을 정의하기 위해 String, CodingKey를 준수하는 enum타입 여러개 정의
  • blog_info, name 두 개의 enum이 필요
  1. 중첩모델에 해당되는 코드들을 String, CodingKey를 준수하는 enum타입으로 선언
    struct MyModel {
      enum CodingKeys: String, CodingKey {
        case userID = "user_id"
        case blogInfo = "blog_info"
      }
      enum BlogInfoKeys: String, CodingKey {
        case name
      }
      enum NameKeys: String, CodingKey {
        case first
        case second
      }
      var userID: String?
      var blogName: String?
    }​
  2. init(from decoder: Decoder) throws 정의
    cf) Encoding 사용할 경우, Encodable을 준수하고 encode(to encoder: Encoder) throws 메소드를 정의
    extension MyModel: Decodable {
      init(from decoder: Decoder) throws {
      // TODO
    }
  3. decoder 인스턴스를 통해 container coding key 획득 
    let codingKeys = try decoder.container(keyedBy: CodingKeys.self)​
  4. nested 되어 있지 않은 프로퍼티는 바로 디코딩 수행
    self.userID = try codingKeys.decode(String.self, forKey: .userID)​
     
  5. nested 되어 있다면 nestedContainer(keyedBy:forKey:)로 접근
    MyModel.blog_info 접근
    let blogInfoKeys = try codingKeys.nestedContainer(keyedBy: BlogInfoKeys.self, forKey: .blogInfo)
  6. MyModel.blog_info.name 접근
    let nameKeys = try blogInfoKeys.nestedContainer(keyedBy: NameKeys.self, forKey: .name)
  7. MyModel.blog_info.name.first 접근
    self.blogName = try nameKeys.decode(String.self, forKey: .first)​

* MyModel 전체 코드

struct MyModel {
  enum CodingKeys: String, CodingKey {
    case userID = "user_id"
    case blogInfo = "blog_info"
  }
  enum BlogInfoKeys: String, CodingKey {
    case name
  }
  enum NameKeys: String, CodingKey {
    case first
    case second
  }
  var userID: String?
  var blogName: String?
}

extension MyModel: Decodable {
  init(from decoder: Decoder) throws {
    let codingKeys = try decoder.container(keyedBy: CodingKeys.self)
    self.userID = try codingKeys.decode(String.self, forKey: .userID)
    
    let blogInfoKeys = try codingKeys.nestedContainer(keyedBy: BlogInfoKeys.self, forKey: .blogInfo)
    let nameKeys = try blogInfoKeys.nestedContainer(keyedBy: NameKeys.self, forKey: .name)
    self.blogName = try nameKeys.decode(String.self, forKey: .first)
  }
}

사용하는 쪽

class ViewController: UIViewController {
  
  let jsonSample = """
{
  "user_id": "jake",
  "blog_info": {
    "name": {
      "first": "iOS 앱 개발 알아가기",
      "second": "SwiftUI 앱 개발 알아가기"
    }
  }
}
"""
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    guard
      let data = self.jsonSample.data(using: .utf8),
      let myModel = try? JSONDecoder().decode(MyModel.self, from: data)
    else { return }
    
    print(myModel.blogName) // iOS 앱 개발 알아가기
  }
}

 

* 전체 코드: https://github.com/JK0369/ExDecoding

 

 

 

Comments