ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [iOS] - Cocoa Touch μ—μ„œ JSON 닀루기
    iOS/πŸ€– App 2021. 5. 7. 23:27

    1μ°¨ μˆ˜μ •: 2021.05.25

    JSON = JavaScript Object Notation

     

    πŸ€” 사전 지식 - JSON μ΄λž€?

    λ‹¨μˆœν•˜κ²Œ 데이터λ₯Ό ν‘œν˜„ν•˜λŠ” 방법 쀑 ν•˜λ‚˜μ΄λ‹€. (톡신 방법도 μ•„λ‹ˆκ³ , ν”„λ‘œκ·Έλž˜λ° 문법도 μ•„λ‹ˆλ‹€. 포맷이닀 !!!!)

     

    μ„œλ²„μ™€ ν΄λΌμ΄μ–ΈνŠΈ κ°„μ˜ 데이터 κ΅ν™˜μ—μ„œ 일반적으둜 많이 μ‚¬μš©λœλ‹€.

    ν΄λΌμ΄μ–ΈνŠΈκ°€ API Request λ₯Ό 보내면 μ„œλ²„κ°€ μ‘λ‹΅μœΌλ‘œ JSON 데이터λ₯Ό 보내쀀닀.

     

    JSON의 포맷은 μžλ°”μŠ€ν¬λ¦½νŠΈ 객체 ν‘œκΈ°λ²•μ„ λ”°λ₯Έλ‹€

    1. key - value μŒμ„ 이루어 ν‘œν˜„ν•˜λ©°, keyλŠ” λ¬Έμžμ—΄μ΄λ‹€. λ¬Έμžμ—΄μ€ " " μŒλ”°μ˜΄ν‘œλ₯Ό μ‚¬μš©ν•˜μ—¬ ν‘œκΈ°ν•œλ‹€.
    2. { } 와 내뢀에 key-value 둜 κ΅¬μ„±λœλ‹€. 즉 Swift λ”•μ…”λ„ˆλ¦¬μ™€ μœ μ‚¬ν•œ ꡬ쑰이닀.
    3. JSONν˜•μ‹μ—μ„œλŠ” null, number, string, array, object, boolean을 μ‚¬μš©ν•  수 μžˆλ‹€.

    λ‹€μ‹œ ν•œλ²ˆ λ§ν•˜μ§€λ§Œ JSON은 데이터 ν‘œν˜„ 방식일 뿐이며, ν…μŠ€νŠΈν˜•μ‹μ˜ 데이터 이닀.

     

    κ·Έλ ‡κΈ° λ•Œλ¬Έμ— 가독성이 μ’‹μœΌλ©°, λ‹¨μˆœν•œ ν…μŠ€νŠΈμ΄κΈ° λ•Œλ¬Έμ— νŠΉμ • 언어에 μ’…μ†λ˜μ§€ μ•ŠλŠ”λ‹€.

     

    λŒ€λΆ€λΆ„μ˜ ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄μ—μ„œ JSON 포맷 데이터λ₯Ό 핸듀링 ν•  수 μžˆλŠ” 라이브러리λ₯Ό μ œκ³΅ν•œλ‹€


     

    πŸ€”μ‚¬μ „ 지식 - JSON 이전에 μ‚¬μš©ν•˜λ˜ XML은?

    νŠΉμ§• 데이터 κ°’ μ–‘μͺ½μœΌλ‘œ νƒœκ·Έκ°€ μžˆλ‹€. νƒœκ·Έλ‘œ ν‘œν˜„λ˜λŠ” 데이터이닀.

     

    이것은 HTML을 기반으둜 κ³ μ•ˆλ˜μ—ˆκΈ° λ•Œλ¬ΈμΈλ°, νƒœκ·Έλ₯Ό 쀄인닀 해도 μ΅œμ†Œν•œ ν‘œν˜„ν•˜λ €λ©΄ μ–‘μͺ½μ— λͺ‡ κΈ€μžμ”©μ΄ μžˆμ–΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ— λ‚΄κ°€ 봣을 λ•Œ μž‘μ„±λ„ μ–΄λ ΅κ³  가독성도 쒋지 μ•Šλ‹€.

     

     

    참고자료 : https://velog.io/@surim014/JSON


    κ°œμš”

    λ„€νŠΈμ›Œν¬ 연결을 ν†΅ν•œ 데이터 μ „μ†‘μ΄λ‚˜, λ””μŠ€ν¬μ— 데이터λ₯Ό μ €μž₯ν•˜κ±°λ‚˜, API μ„œλ²„λ‚˜ μ„œλΉ„μŠ€λ‘œ 데이터λ₯Ό μ œμΆœν•˜λŠ” 것과 κ΄€λ ¨λœ ν”„λ‘œκ·Έλž˜λ° μž‘μ—…λ“€μ΄ λ§ŽμŠ΅λ‹ˆλ‹€.

     

    이런 μž‘μ—…λ“€μ€ μ€‘κ°„μž 역할을 ν•  수 μžˆλŠ” ν˜•μ‹μœΌλ‘œμ˜ λ°μ΄ν„°μ˜ 인코딩 κ³Ό 디코딩을 ν•„μš”λ‘œ ν•©λ‹ˆλ‹€.

    인코딩과 λ””μ½”λ”©?

     

    μ™œλƒλ©΄ μŠ€μœ„ν”„νŠΈλ‘œ ν‘œν˜„λœ 데이터λ₯Ό λ‹€λ₯Έ ν™˜κ²½μ—μ„œλŠ” ν•΄μ„ν•˜κΈ° μ–΄λ ΅κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

     

    Swift standard library λŠ” 데이터 인코딩 κ³Ό λ””μ½”λ”©μ˜ ν‘œμ€€ν™”λœ 방법을 이미 μ •μ˜ν•΄λ†“μ•˜μŠ΅λ‹ˆλ‹€.

     

    μš°λ¦¬λŠ” ν•„μš”ν•œ μ»€μŠ€ν…€ νƒ€μž…μ— 이 방법을 μ μš©ν•  것인데,  μ΄κ²ƒμ€ Encodable κ³Ό Decodable ν”„λ‘œν† μ½œμ˜ κ΅¬ν˜„μ„ ν†΅ν•΄μ„œ κ°€λŠ₯ν•©λ‹ˆλ‹€.

     

    일단 μœ„ ν”„λ‘œν† μ½œμ„ κ΅¬ν˜„ ν•˜λ©΄ Encoder 와 Decoder κ°€ 데이터λ₯Ό 가지고 JSON μ΄λ‚˜ property list 와 같은 external representation* 으둜 인코딩 & 디코딩을 ν•΄μ€λ‹ˆλ‹€.

    πŸ’‘μŠ€μœ„ν”„νŠΈ μ™ΈλΆ€μ—μ„œ μ‚¬μš©λ˜λŠ” 데이터 ν˜•μ‹μ„ external representation 이라고 ν‘œν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

     

    Encodable κ³Ό Decodable 을 λ™μ‹œμ— μ±„νƒν•œλ‹€λ©΄ 인코딩과 λ””μ½”λ”© μž‘μ—… λͺ¨λ‘λ₯Ό ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

     

    그리고 그것은 protocol composition(&) 을 ν†΅ν•˜μ—¬ Codable 둜 μ •μ˜λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

    typealias Codable = Decodable & Encodable // μ‹€μ œλ‘œ μ΄λ ‡κ²Œ κ΅¬ν˜„λ˜μ–΄ 있음 πŸ˜‰

     

     

    본둠으둜 λŒμ•„μ™€μ„œ...

    JSON Encoding κ³Ό Decoding 을 ν•˜λŠ” μ£Όμ²΄λŠ” JSONEncoder 와 JSONDecoder μ˜€λΈŒμ νŠΈμž…λ‹ˆλ‹€.

     

    Encoding κ³Ό Decoding 이 κ°€λŠ₯ν•˜λ €λ©΄ λŒ€μƒ struct λŠ”Encodable,Decodable ν”„λ‘œν† μ½œμ„ μ±„νƒν•˜κ³  μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.

     

    λ§Œμ•½ Encodable κ³Ό Decodable 을 λͺ¨λ‘ μ±„νƒν•œλ‹€λ©΄, Codable ν”„λ‘œν† μ½œμ„ μ±„νƒν•œλ‹€κ³  λͺ…μ‹œν•˜λ©΄ λ©λ‹ˆλ‹€.

     

    μ–΄λ–€ μ»€μŠ€ν…€ νƒ€μž…μ„ codable ν•˜κ²Œ λ§Œλ“œλŠ” κ°€μž₯ μ‰¬μš΄ 방법은, κ·Έ νƒ€μž…μ΄ 가진 속성듀을 λͺ¨λ‘ Codable νƒ€μž…μœΌλ‘œ μ •μ˜ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

     

    Codable ν•œ νƒ€μž…μ€ standart library νƒ€μž…μΈ  String, Int, Double ν˜Ήμ€ Foundation νƒ€μž…μΈ Date, Data, URL 등이 μžˆμŠ΅λ‹ˆλ‹€.

     

    이런 νƒ€μž…λ“€μ˜ μ†μ„±μœΌλ‘œλ§Œ κ΅¬μ„±λœ νƒ€μž…μ€ Codable ν”„λ‘œν† μ½œμ„ λ”°λ₯Έλ‹€κ³  λͺ…μ‹œλ§Œν•˜λ©΄ λ©λ‹ˆλ‹€.

    "automatically conforms to Codable just declaring that conformance."

    struct Landmark: Codable {
      var name: String
      var foundingYear: Int
      
      // 속성듀이 이미 Codable ν•˜λ―€λ‘œ, ν”„λ‘œν† μ½œ κ΅¬ν˜„μ΄ ν•„μš” μ—†μŠ΅λ‹ˆλ‹€.
      
      var location: Coordinate
      
      // λ§Œμ•½ μ»€μŠ€ν…€ νƒ€μž…(Coordinate)이 μΆ”κ°€λœλ‹€κ³  ν•˜λ”λΌλ„, κ·Έ νƒ€μž…μ„ Codableν•˜κ²Œ μ •μ˜ν•œλ‹€λ©΄ 문제 μ—†μŠ΅λ‹ˆλ‹€.
      
      var metadata: [String: String]
      
      // Array, Dictionay, Optional 같은 Built-in type은 κ·Έ λ‚΄λΆ€ νƒ€μž…μ΄ Codable 일 경우 Codable이 λ©λ‹ˆλ‹€.
    }
    

     

     


    let encoder = JSONEncoder()
    encoder.outFormatting = .prettyPrinted
    encoder.keyEncodingStrategy = .converToSnakeCase
    
    do {
    // 1
    let jsonData = try encoer.encode(someStruct)
    
    // 2
      if let str = String(data: jsonData, encoding: .utf8) {
        print( str ) 
      }
    } catch {
      print(error)
    }

    1 : encode() λŠ” Throwing Method μ΄λ―€λ‘œ try 둜 μ²˜λ¦¬ν•΄μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.

    그리고 try λŠ” do catch 둜 μ²˜λ¦¬ν•©λ‹ˆλ‹€.

    λ°˜ν™˜ 값은 λ°”μ΄λ„ˆλ¦¬ 데이터 ν˜•νƒœμ˜ JSON μž…λ‹ˆλ‹€.

     

    2 :

    λ°”μ΄λ„ˆλ¦¬ λ°μ΄ν„°λŠ” 인간이 읽을 수 μ—†κΈ° λ•Œλ¬Έμ— 인코딩이 ν•„μš”ν•©λ‹ˆλ‹€.

    눈으둜 ν™•μΈν•˜λ €λ©΄ String(data:encoding:) μƒμ„±μžλ‘œ λ¬Έμžμ—΄ μΈμŠ€ν„΄μŠ€λ‘œ 생성해 좜λ ₯ν•΄λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

    λ˜ν•œ outFormatting 속성을 .prettyPrinted 둜 λ³€κ²½ν•΄ key-value κ°„ κ°œν–‰μ„ μΆ”κ°€ν•˜μ—¬ 가독성을 높일 수 μžˆλ‹€.


     

    JSON Encoding

    Encoding 은 Struct μΈμŠ€ν„΄μŠ€μ—μ„œ JSON λ°μ΄ν„°λ‘œ λ³€ν™˜ν•˜λŠ” κ³Όμ •μž…λ‹ˆλ‹€.

     


     

    속성 이름과 key 이름 맀칭

    JSON μ—μ„œ ν‚€ 넀이밍 μ»¨λ²€μ…˜μ€ snake_case 이닀.

     

    ν•˜μ§€λ§Œ Swift μ—μ„œλŠ” lowerCamelCase λ₯Ό μ‚¬μš©ν•˜κΈ° λ•Œλ¬Έμ— μˆ˜μ •μ—†μ΄ Encoding 을 μ§„ν–‰ν•œλ‹€λ©΄ key name 이 lowerCamelCase 둜 λ“€μ–΄κ°€κΈ° λ•Œλ¬Έμ— ν˜Έν™˜μ„±μ΄ λ–¨μ–΄μ§ˆ 것이닀.

     

    μ΄λ•ŒλŠ” keyEncodingStrategy 속성을 λ³€κ²½ν•˜μ—¬ λŒ€μ‘ν•  수 μžˆλ‹€.

    encoder.keyEncodignStrategy = .convertToSnakeCase

     

    λ‚ μ§œ ν˜•μ‹ 맀칭

    Swift λŠ” Date νƒ€μž…μ˜ 속성을 JSON 으둜 인코딩 ν• λ•Œ, Double κ°’μœΌλ‘œ μΈμ½”λ”©ν•œλ‹€.

     

    이 값은 2001λ…„ 1μ›” 1일을 μ‹œμž‘μ μœΌλ‘œ 떨어진 만큼의 μ‹œκ°„μ„ ν‘œν˜„ν•˜λŠ”λ°, 초 λ‹¨μœ„μ΄λ‹€.

     

    가독성이 맀우 λ–¨μ–΄μ§€λ―€λ‘œ 많이 μ‚¬μš©ν•˜λŠ” iso8601 ν˜•μ‹μ„ λ”°λ₯Έ λ¬Έμžμ—΄λ‘œ μΈμ½”λ”©ν•˜κ²Œ 바꿔쀄 수 μžˆλ‹€.

     

    인코더 였브젝트의 dateEncodingStrategy 속성을 λ³€κ²½ν•˜λ©΄ λœλ‹€.

    encoder.dateEncodingStrategy = .iso8601

    그런데 이 ν˜•μ‹μ„ κ·ΈλŒ€λ‘œ λ”°λ₯΄μ§€ μ•ŠλŠ” API 듀이 μžˆλ‹€. (예: Kakao REST API)

     

    이 κ²½μš°μ— ν•΄λ‹Ή Key ν˜Ήμ€ 속성은 Encoding ν˜Ήμ€ Decoding 에 μ‹€νŒ¨ν•˜κ²Œ λœλ‹€.

     

    μ΄λ•ŒλŠ” DateFormatter μΈμŠ€ν„΄μŠ€λ₯Ό 직접 λ§Œλ“€μ–΄μ„œ encoder 에 μ „λ‹¬ν•΄μ•Όν•œλ‹€.

    // 1. Custom DateFormatter λ₯Ό μƒμ„±ν•œλ‹€.
    var dateFormatter: DateFormatter = {
        var formatter = DateFormatter()
        formatter.dateFormat = "MMMyyyy" // dateFormat ν˜•μ‹λ¬Έμžμ—΄
    }()
    
    // 2. encoder μ˜€λΈŒμ νŠΈμ— .formmated case의 μ—°κ΄€κ°’μœΌλ‘œ μ „λ‹¬ν•œλ‹€.
    encoder.dateEncodingStrategy = .formmatted(dateFormatter)
    

    🍯 그런데 μ›ν•˜λŠ” DateFormat ν˜•μ‹λ¬Έμžμ—΄μ„ 직접 μž‘μ„±ν•˜λŠ” 것이 μ–΄λ ΅κ²Œ 느껴질수 μžˆλ‹€.

     

    이 λ•ŒλŠ” https://nsdateformatter.com μ—μ„œ μ›ν•˜λŠ” ν˜•μ‹λ¬Έμžμ—΄μ„ κ°„νŽΈν•˜κ²Œ 생성할 수 μžˆλ‹€.


     

    JSON Decoding

    Decoding은 μ„œλ²„λ‘œλΆ€ν„° 전달받은 JSON 데이터λ₯Ό Struct μΈμŠ€ν„΄μŠ€λ‘œ λ³€ν™˜ν•˜λŠ” 과정이닀.

    guard let jsonData = jsonStr.data(using: .utf8) else {
       fatalError()
    }
    
    //
    let decoder = JSONDecoder()
    let result = try? decoder.decode(Person.self, from: jsonData)

    Struct μ†μ„±μ˜ νƒ€μž…

    일반 νƒ€μž…μ΄λΌλ©΄ decoding μ‹€νŒ¨ μ‹œ λŸ°νƒ€μž„ 였λ₯˜κ°€ λ°œμƒν•œλ‹€

    μ˜΅μ…”λ„ νƒ€μž…μ΄λΌλ©΄ decoding μ‹€νŒ¨ μ‹œ nil 둜 μ΄ˆκΈ°ν™”λœλ‹€.


     

    JSON key 와 struct 속성이 λ‹€λ₯Έ 경우

    λ§Œμ•½ ν‚€ λ„€μž„κ³Ό 속성 μ΄λ¦„μ˜ Naming convention 만 λ‹€λ₯΄λ‹€λ©΄ encoding λ•Œμ™€ 같은 λ°©μ‹μœΌλ‘œ ν•΄κ²°ν•΄μ£Όλ©΄ λœλ‹€.

    decoder.keyDecodingStrategy = .convertFromSnakeCase

     

    ν•˜μ§€λ§Œ 이름이 μ•„μ˜ˆ λ‹€λ₯Έκ²½μš°λŠ” custom key mappping 을 ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€.

     

    CodingKey 에 λŒ€ν•œ λ‚΄μš©μ€ λ”°λ‘œ κΈ€λ‘œ μ •λ¦¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€

     

    struct iPhone: Codable {
       var SerialNumber: String
       var Storage: Int
       var generation: Int
       var ramSize: Int
    
      enum CodingKeys: String, CodingKey {
        case SerialNumber
        case Storage
        case generation = "model_number"
        case ramSize
      }
    }

    예λ₯Ό λ“€μ–΄ JSON λ°μ΄ν„°μ—μ„œ model_number 킀에 μ €μž₯된 값을 generation 속성에 λ°›μ•„μ˜€κ³  싢을 λ•Œ, custom Key mapping 을 ν•˜μ§€ μ•ŠμœΌλ©΄ decoding error κ°€ λ°œμƒν•  것이닀. Int νƒ€μž…μΈ generation 에 λ§€ν•‘λœ 값이 μ—†κΈ° λ•Œλ¬Έμ΄λ‹€.

     

    μœ„μ˜ μ½”λ“œμ™€ 같이 ν˜•μ‹ 내에 enum 을 μ„ μ–Έν•˜κ³ , raw Value 둜 String 둜 μ„ μ–Έν•˜κ³ , CodingKey ν”„λ‘œν† μ½œμ„ μ±„νƒν•œλ‹€μŒ λ§€ν•‘ν•˜κ³  싢은 case 만 rawValue 섀정을 ν•΄μ£Όλ©΄ ν•΄κ²°λœλ‹€. rawValue 에 λ“€μ–΄κ°€λŠ” 값은 λ§€ν•‘ν•˜κ³  싢은 JSON 의 key name 이닀.


     

    🍯 Custom Encoding & Custom Decoding

    보톡 인코딩과 디코딩에 μ œμ•½μ„ λ„£κ³  μ‹Άμ„λ•Œ μ»€μŠ€ν…€μœΌλ‘œ κ΅¬ν˜„ν•œλ‹€.

     

    Encodable & Decodable ν”„λ‘œν† μ½œμ˜ λ©”μ„œλ“œλ₯Ό 직접 κ΅¬ν˜„ν•˜λ©΄ λœλ‹€.

     

    " πŸ₯³ 원리λ₯Ό μ œλŒ€λ‘œ μ΄ν•΄ν–ˆμœΌλ‹ˆ, μ»€μŠ€ν…€μ΄ ν•„μš”ν•  λ•Œ λ‚΄μš©μ„ 더 μΆ”κ°€ν•˜μž! "


     

    πŸ’‘ κΈ°λ³Έ 원리: protocol method λ₯Ό 직접 κ΅¬ν˜„ν•˜κ³ , container 의 unkeyed-data λ₯Ό μ μ ˆν•˜κ²Œ encode/decode ν•˜μž!!!!

    Encodable 의 경우 encode(to encoder:) Decodable 의 경우 init(from decoder:) λ©”μ„œλ“œλ₯Ό 직접 κ΅¬ν˜„ν•˜λ©΄ λœλ‹€.

      func encode(to encoder: Encoder) throws {
        <#code#>
      }
    
      init(from decoder: Decoder) throws {
        <#code#>
      }

    μš°μ„  μ»€μŠ€ν…€ 인코딩을 ν•˜λ €λ©΄ CodingKey ν”„λ‘œν† μ½œμ„ μ±„νƒν•œ CodingKeys μ—΄κ±°ν˜•μ„ 생성해야 ν•œλ‹€.

    struct Person: Codable {
       var firstName: String
       var lastName: String
       var age: Int
       var address: String?
    
      enum CodingKeys: String, CodingKey {
        case firstName
        case lastName
        case age
        case address
      }

     

    μ™œ? container(keyedBy:) 의 νŒŒλΌλ―Έν„°λ‘œ CodingKey 의 메타 νƒ€μž…μ„ λ°›λŠ”λ‹€.

     

      func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
    
        try container.encode(firstName, forKey: .firstName)
    
        try container.encode(lastName, forKey: .lastName)
    
        guard (30...60).contains(age) else { // μ œμ•½μ„ μΆ”κ°€ν•˜κ³  쑰건에 λ§žμ§€ μ•ŠλŠ”λ‹€λ©΄ μ—λŸ¬λ₯Ό λ˜μ§„λ‹€.
          throw DecodingError.invalidRange
        }
    
        try container.encodeIfPresent(address, forKey: .address)
      }

     

    μ»¨ν…Œμ΄λ„ˆμ˜ μ •μ˜ : " an container appropriate for holding multiple unkeyed values."

     

    그리고 encode(_, forKey:) λ©”μ„œλ“œλ‘œ container λ‚΄λΆ€μ˜ 데이터(unkeyed values)듀을 μΈμ½”λ”©ν•˜μ—¬ 킀와 맀핑해쀀닀.

     

    encode λŠ” throws ν‚€μ›Œλ“œμ—μ„œ 볼수 μžˆλ“―μ΄ Throwing Method 둜, λ‚΄λΆ€ λΈ”λŸ­μ—μ„œ μ—λŸ¬λ₯Ό 던질 수 μžˆλ‹€.

     

    μœ„ μ½”λ“œμ—μ„œλŠ” age μ†μ„±μ˜ 값이 30~60 사이가 μ•„λ‹ˆλΌλ©΄ 미리 μ •μ˜λœ μ—λŸ¬λ₯Ό λ˜μ§€κ³  μžˆλ‹€.

     

     

     

    끝!


    πŸ€–πŸŸ’[우짱의 iOS λΈ”λ‘œκ·Έ]πŸ”΅πŸ’»

    iOSλ₯Ό κ³΅λΆ€ν•˜λ©΄μ„œ 배운 λ‚΄μš©μ„ κΈ°λ‘ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

     

    참고 자료: https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding

    λŒ“κΈ€

μ–΄μ œλ³΄λ‹€ λ°œμ „ν•œ λ‚˜