iOS 개발일기

[Swift] JSONParser - JSONSerialization 본문

iOS/Swift

[Swift] JSONParser - JSONSerialization

맨날 까먹으니 적어두자 2022. 3. 16. 23:12

오늘은 JSON 파싱 방법 중 하나인 JSONSerialization에 대해서 알아보겠습니다.

 

JSONSerialization은 Decodable에 비해 제약이 심하지 않죠.

그렇지만 Decodable이 워낙 간단하게 구현할 수 있는지라...

 

기본적인 JSONSerialization

먼저 문자열을 하나 선언해주겠습니다.

let jsonString = """
                 {
                     "user_id"   : 0,
                     "user_name" : "hoon",
                     "user_age"  : 30
                 }
                 """

 

그 다음 JSONSerialization을 해보도록 하겠습니다.

 

guard let data = jsonString.data(using: .utf8) else {return}
do {
    guard let dic = try JSONSerialization.jsonObject(with: data) as? [String:Any] else {return}
    
    if let userId = dic["user_id"] as? Int {
        print(userId)
    }
    
    if let username = dic["user_name"] as? String {
        print(username)
    }
    
    if let age = dic["user_age"] as? Int {
        print(age) 
    }
    
} catch {
    print("parse error:", error)
}

현재에는 데이터에 하나의 Dictionary만 존재하기 때문에 [String:Any] 타입으로 캐스팅해주는 것입니다.

(그리고 바인딩은 캐바캐지만 저같은 경우에는 되도록 바인딩을 통해서 예외처리를 해주는 편입니다.)

 

만약, 받아야 하는 데이터가 배열일 경우에는 배열로 캐스팅해주셔야합니다.

guard let arr = try JSONSerialization.jsonObject(with data) as? [[String:Any]] else {return}

이렇게 Array<Dictionary<String:Any>>로 해주셔야 됩니다.

(이 외에도 다른 형태들이 다양하게 존재하기 때문에 데이터의 형태를 잘 파악하신 다음에 캐스팅해주셔야 데이터를 받을 수 있습니다.)

 

자 기본적인 사용법을 봤으니 이제 데이터를 모델로 사용해보는 방법을 알아보겠습니다.

데이터를 담을 모델을 생성해보겠습니다.

Struct User {

     private(set) uid: Int
     private(set) name: String
     private(set) age: Int
     
     init(from dic: [String:Any]) {
         self.uid = (dic["user_id"] as? Int) ?? 999
         self.name = (dic["user_name"] as? String) ?? ""
         self.age = (dic["user_age"] as? Int) ?? 0
     }
}

 

 

필요한 데이터는 모두 적어주시고 Dictionary를 받아 초기화해줄 예정입니다.

 

JSONSerialization 사용은 기본적인 방법과 별로 다르지않습니다.

guard let data = jsonString.data(using: .utf8) else {return}
do {
    guard let dic = JSONSerialization.jsonObject(with: data) as? [String:Any] else {return}
    let user = User(from: dic)
    print(user.uid)  //0
    print(user.name) //hoon
    print(user.age)  //30
} catch {
    print("parse error:", error)
}

이렇게 간단하게 Instance를 사용할 수 있습니다.

 

지금까지는 기본적인 JSONSerialization을 사용하는 법을 알아보았습니다.

 

Decodable이 나온 현재에는 JSONSerialization은 잘 안쓰는 편이긴 합니다만 

받아오는 Data의 값들 중에서 필요한 값이 하나일 경우에는 Decodable Instance를 생성하는 것은 필요한 Data에 비해 너무 과한 비용을 투자하는 것 같아서 JSONSerialization을 사용합니다.

 

예를 들어 보겠습니다.

let jsonString = """
                 {
                     "result" : true,
                     "a"      : "",
                     "b"      : 1
                 }
                 """

좀 극단적인 예이기는 합니다만..ㅎㅎ

이런 JSON에서 나에게 'result'라는 데이터만 필요할 경우에는 Decodable Instance를 생성하기에는 배보다 배꼽이 큰 경우인 것 같아

 

JSONSerialization을 통해서 하나의 값만 가져오게 설계를 하는 것 같아요.

guard let data = jsonString.data(using: .utf8) else {return}
do {
    guard let dic = try JSONSerialization.jsonObject(with: data) as? [String:Any] else {return}
    
    if let result = dic["result"] as? Bool {
       print(result) //true
    }
    
} catch {
    print("parse error:", error)
}

이렇게 말이죠

 

함수를 만들어서 범용적으로 사용할 수 있게 만들어준다면 데이터 안에 하나의 파라미터만 필요로 할 때에는

Decodable을 사용하는 것 보다 JSONSerialization이 조금 더 낫지않나 이런 생각을 하면서 사용하고 있습니다...

(제 의견과 다른 분이 있으시다면 댓글에 이유를 알려주세요!)

 

더보기
func parse<T>(_ type: T.Type, from jsonString: String) -> T? {
    guard let data = jsonString.data(using: .utf8) else {return nil}
    do {
        return try JSONSerialization.jsonObject(with: data) as? T
    } catch {
        print("parse error:", error)
    }
    
    return nil
}