본문 바로가기

iOS/DataParsing

[iOS] JSON형식의 데이터 파싱 뿌수기!! with URLRequest, URLSession #1

 

JSON이란?

https://www.json.org/json-ko.html

 

JSON

JSON (JavaScript Object Notation)은 경량의 DATA-교환 형식이다. 이 형식은 사람이 읽고 쓰기에 용이하며, 기계가 분석하고 생성함에도 용이하다. JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1

www.json.org

JSON은 자바스크립트 객체 만들때 사용되는 표현 방법 입니다. (Swift의 딕셔너리 같은 성격!!) 데이터를 저장하고나, 전송할때 C,C++, 웹서버, db등 많이 사용된다. key: value타입으로 이루어져있고 배열 또한 가능합니다. 

크게 JSON 객체{} 로 이루어져있고 JSON 객체[] 로 이루어져있습니다.

// root object가 객체로 이루어진 형식
{	
	"result" : SUCCEESS",
	"swift" : [
    	{
    		"study" : "json",
        	"hour"  : "3"
    	},
    	{
    		"study" : "Codable",
        	"hour"  : "36"
    	}
    ]
}

// root object가 배열로 이루어진 형식
[
   {
	"study" : "Dijkstra",
        "hour"  : "10"
    },
    {
    	"study" : "graph",
        "hour"  : "30"
    }
]

저는 JSON을 하면 두번째로 딕셔너리가 떠오르지만 첫번째로 떠오르는 것은 REST API입니다. HTTP를 위한 아키텍쳐인데 네트워크 자원을 정의하고 주소를 관리하기위해 정의된 인터페이스?라고 합니다. GET, POST, PUT, DELETE등 다양한 HTTP메소드를 사용해서 요청하고 응답받고 자원 업데이트도 가능하다는 사실!! RESTful API에서 주고 받는 메시지들은 JSON형식으로 구성해서 요청, 응답받기 때문에 JSON형식을 잘 이해해야 서버의 데이터나 API 데이터를 JSON형식으로 받아서 객체로 사용할 수 있습니다.

 

참고로 이때 Swift는 

  • Dictionary 
  • NSDictionary ( Dictionary보다 NSDictionary요구하는 메소드 많음. 편집x)
  • NSMutableDictionary (편집o)

HTTP 메시지 구성 요소

 

REST API 데이터를 JSON으로 파싱하기 위해서는 HTTP 메시지의 Line, Header, body 구조를 알아야 합니다.

헤더

  • Line(라인)

가장 중요한 전송 방식입니다. GET과 POST의 차이를 모르고 GET을 필요로 하는 상황에서 POST를 사용해서 계속 데이터를 호출하거나 요청한다면 참 난감할 것입니다...... 

GET : 클라이언트(앱 화면!!)에서 서버로 정보를 요청하기 위해 사용되는 메서드 (== 서버에서 값을 받아오고 싶다)

POST : 클라이언트(application)에서 서버로 리소스(데이터) 갱신(업데이트)하거나 새로 생성할 때 사용되는 메서드.

  • Header(헤더)

POST인지 GET인지에 따라 헤더 구성이 다릅니다. 메세지 본문에 대한 메타정보(부가적인 정보)를 키 값 형태로 추가합니다. 대표적으로 "Content-Type" : "application/json"을 볼 수 있습니다.

  • Body(바디)

본문! 요청 메시지를 이곳에 작성합니다. 페이지는 몇개 요구 하는지, 로그인등등 요청 또는 응답 메세지를 바디에 작성합니다.


URLRequest, URLSession

위에서 소개한 HTTP메소드의 라인, 헤더, 바디가 Swift에선 URLRequest 객체의 값으로 정의됩니다. 참고로 값을 파싱할 때는 Content-Type를 application/json으로 해주어야 데이터를 JSON의 형식에 맞게 해석할 수 있습니다.

이후에 request를 URLSession의 dataTask를 통해 데이터를 요청, 응답할 수 있습니다.

// root object가 array로 이루어진 형식 - 예제!!
[	
    {
    	"study" : "json",
        "hour"  : "3"
    },
    {
    	"study" : "Codable",
        "hour"  : "36"
    }
]

서버의 데이터가 위와 같은 데이터(root object as Array)로 구성된다고 가정하겠습니다.

 

url = "https://swiftBBUSUGI.com/study" 일 때 서버에 저장된 데이터 형식은 위와 같은 형식으로 가정했을 때 해당 데이터를 앱으로 가져오려면(==데이터를 파싱한다)

 

  1. 데이터에 맞는 Model 구조체, 클래스등을 선언해 데이터를 추론한다.
  2. URL 객체를 만든다.
  3. reqeust를 통해 서버에서 요구되는, 요구하는 라인, 헤더, 바디를 구성한다.
  4. URLSession을 통해 데이터를 파싱해오는 작업을 수행한다.
  5. 파싱해온 Data를 JSONSerialization을 통해 JSON형식으로 변환해서 저장한다!
func getData(){
    // 1.
	struct TodayStudy{
		let study : String
    	let hour  : String
	}
    //2. (주소는 예시)
    guard let url = URL(string: "https://swiftBBUSUGI.com/study") else {
        return
    }
    //3.
    guard var request = try? URLRequest(url: url, method: .get) else {
        print("Can't get URLRequest instance")
        return
    }
    request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
    //4.
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let _data = data, error == nil else {
        print("domaiError")
        return
        }
        // 5. 데이터가 존재할 경우에 JSONSerializaiton을 통해 Data를 JSON형식으로 파싱!!
        guard let json = try? JSONSerialization.jsonObject(with: _data, options: []) as? [[String:Any]] else {
        print("json을 TodayStudy로 변환 실패")
        return
        }
        print("succeess : \(json)")
    }
    task.resume()
}

 

마지막으로 resume()은 꼭 해주어야 dataTask(with:)가 작동합니다.

 

여기서 주의깊게 봐야할 것이 데이터를 파싱할 때 어느 타입으로 캐스팅 해야 하는지 생각해야합니다. 맨 위에서 소개한 root object가 객체로 이루어져 있을 때, root object as array일때를 소개했는데요. 5단계의 JSONSerialization을 통해 JSON데이터를 다루기위해 캐스팅할 때 

  • root object가 object라면 [String:Any] 타입으로

... JSONSerialization.jsonObject(with:_ data, options : []) as? [String:Any] 타입으로

이때 [String:Any] 대신 NSDictionary나 NSMutableDictionary다 가능하다는 사실

  • root object가 array라면 [[String:Any]] 타입으로 == [NSDictionary]

왜죠?

root object가 객체일 때

딕셔너리는  let dict: [Stirng, Any] = ["study" : "json", "hour" : "3000"] 이건 단순히 key, value로 선언된 말 그대로 NSDictrionary타입을 의미합니다. 

 

root object가 배열 일때 

[ {딕셔너리},{딕셔너리} ]

let dict: [[String,Any]] =  [ ["study" : "json", "hour" "3000"], ["study" : "codable", "hour" : "1"] ] 

 

이렇게 딕셔너리가 여러개로 선언된 [딕셔너리] == [NSDictionary] == [[String : Any]] 형식을 뜻한다는 것!!


 

JSONSerialization

마지막으로 JSONSerialization 클래스는 JSON형식의 데이터를 Foundation Object로 변환하거나 Foundation Object를 JSON 형식의 데이터로 변환해주는 클래스입니다.

 

서버에서 REST API를 GET방식으로 요청할 때는 JSONSerialization의 jsonObject(with:options:)메소드를 써야 딕셔너리 또는 배열 형태로 반환이 가능합니다.

POST방식으로 전송할 때는 Foundation Object를 JSONSerialization의 data(withJSONObject:options:)를쓰면 JSON 데이터로 변환 가능합니다.