Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
Computer Science

JSON Parsing

by Toughie 2023. 4. 16.

JSON이란?

JavaScriptObjectNotation의 약자.

 

JSON의 문법이 자바스크립트 문법과 유사하지만 자바스크립트에서만 사용되는 것이 아니라 
JSON Parsing을 지원하는 프로그래밍 언어에서는 다 사용할 수 있다.

 

인간이 읽고 쓰기 쉬우며, 컴퓨터가 구문을 분석하고 생성하기 쉬운 '가벼운 데이터 교환 형식'을 말한다.

보통 서버/웹 응용 프로그램 간 데이터 전송 및 텍스트 파일 형식으로 데이터를 저장하는데 사용된다.

 

JSON 데이터는 key - value 쌍으로 되어있다.

 

key - 항상 문자열(String)이다.

value - 문자열, 숫자, bool, 배열, 혹은 다른 JSON 객체일 수도 있다.

예시코드를 살펴보며 하나씩 보자.

https://ko.wikipedia.org/wiki/JSON

 {
    "이름": "홍길동",
    "나이": 55,
    "성별": "남",
    "주소": "서울특별시 양천구 목동",
    "특기": ["검술", "코딩"],
    "가족관계": {"#": 2, "아버지": "홍판서", "어머니": "춘섬"},
    "회사": "경기 수원시 팔달구 우만동"
 }

 

먼저 중괄호 { }는 객체(Object)를 의미한다. (객체는 중괄호로 감싸져 있다.)
위의 예시코드는 크게 하나의 객체, 객체 안에 여러 key-value 쌍이 있는 것.

 

또한 key는 항상 String이라고 했기 때문에 "이름", "나이", "성별"... 등으로 문자열을 확인할 수 있다.

또한 key와 value는 쌍을 이룬다고 했는데, key: value 형태로 작성한다.

 

: 우측에 있는 데이터들을 보면 형태가 다양하다.

key는 무조건 String이어야 하지만 value에는 다양한 자료형이 들어갈 수 있기 때문이다.

 

이름: String

나이: Int

성별: String

주소: String

특기: Array<String>  -> 문자열 배열도 value가 될 수 있다.

또한 배열 안에 다양한 자료형이 아래와 같은 형태도 가능하다.

[ 7, "멋쟁이", [17, "힘"], { "Candy": 5} ] 

가족관계: Object

회사: String

 

자 그러면 JSON Parsing은 무슨 의미일까?

JSON 데이터에서 '원하는 정보를 추출' 하는 것을 의미한다.

(ex. open API에서 데이터를 받아오면 꼭 모든 데이터가 필요하지 않은 경우가 많다. 따라서 필요한 정보만 추출하는 것이다.)

(API를 통해 반환받은 JSON데이터를 파싱해서 클라이언트 측(웹/앱)에서 사용하는 것)

 

파싱(Parsing)

https://ko.wikipedia.org/wiki/%EA%B5%AC%EB%AC%B8_%EB%B6%84%EC%84%9D

언어학에서 구문 분석(構文分析, 문화어: 구문해석, 문장해석) 또는 '파싱'은 문장을 그것을 이루고 있는 구성 성분으로 분해하고 그들 사이의 위계 관계를 분석하여 문장의 구조를 결정하는 것을 말한다.

컴퓨터 과학에서 파싱((syntactic) parsing)은 일련의 문자열을 의미있는 토큰(token)으로 분해하고 이들로 이루어진 파스 트리(parse tree)를 만드는 과정을 말한다.

 

쉽게 말하면 JSON 문자열을 읽고 해당 문자열을 JavaScript 객체나 데이터 구조로 변환하는 것을 의미한다.

 

Swift JSON Parsing 예시 코드를 살펴보자.

먼저 Swift에서는 JSONSerialization 클래스를 통해 JSON을 파싱할 수 있다.

이 클래스에는 JSON 데이터를 Swift의 타입에 맞게 변환하는 다양한 메소드가 있다.

//아래와 같은 형태의 JSON 데이터를 받아왔다고 가정
let jsonString = """
{
  "name": "Toughie",
  "age": 20,
  "city": "Seoul"
}
"""

if let jsonData = jsonString.data(using: .utf8) {
//data(using:) 메소드를 사용해 jsonData 변수에 저장된 JSON 문자열을 Data 타입으로 변환
    do {
        let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
        //JSON 데이터를 파싱해 Any 타입으로 변환
        if let dict = jsonObject as? [String: Any] {
        //jsonObejct를 [String: Any] 타입의 딕셔너리로 타입캐스팅
            let name = dict["name"] as? String ?? ""
            //"name"이라는 키의 값이 String으로 타입캐스팅 되면 name 상수에 할당, 캐스팅실패시 빈문자열 할당
            let age = dict["age"] as? Int ?? 0
            let city = dict["city"] as? String ?? ""
            print("Name: \(name), Age: \(age), City: \(city)")
        }
    } catch {
    //JSOnSerialization.jsonObject 메서드가 에러를 던지기 때문에 try - do - catch 에러처리
        print("JSON parsing error: \(error.localizedDescription)")
    }
}

 

Swift4부터 추가된 Codable(Encodable & Decodable) 프로토콜을 활용해 위의 방식보다 더욱 간단하게 파싱할 수도 있다

//아래와 같은 형태의 JSON 데이터를 받아왔다고 가정
let jsonString = """
{
  "name": "Toughie",
  "age": 20,
  "city": "Seoul"
}
"""

 

이 JSON 데이터는 사람의 이름, 나이 도시를 담고 있다.
따라서 간단하게 파싱해서 Person 구조체를 만들어 보자.

struct Person: Codable {
	let name: String
    let age: Int
    let city: String
}
//프로퍼티는 JSON의 키와 일치해야한다.

if let jsonData = jsonString.data(using: .utf8) {
	do {
    //메타타입
    	let Person = try JSONDecoder().decode(Person.self, from jsonData)
        print("Name: \(person.name), Age: \(person.age), City: \(person.city)")
    } catch {
        print("JSON parsing error: \(error.localizedDescription)")
    }
}

JSON 데이터를 Person 객체로 변환하는 과정이다.

 

위와 같은 과정을 수동으로 할 수도 있지만, 아래와 같은 사이트를 이용하면 더욱 편리하게 파싱을 할 수 있다 :)

https://quicktype.io/

 

Convert JSON to Swift, C#, TypeScript, Objective-C, Go, Java, C++ and more<!-- --> • quicktype

{ "people": [ { "name": "Atticus", "high score": 100 }, { "name": "Cleo", "high score": 900 }, { "name": "Orly" }, { "name": "Jasper" } ] } Provide sample JSON files, URLs, JSON schemas, or GraphQL queries.

quicktype.io

 

+ 관련개념
코딩키(Coding Key) 

코딩키는 JSON 데이터를 Swift 객체로 디코딩할 때 사용되는 문자열 식별자이다.

코딩키는 일반적으로 JSON 키와 일치하며 객체의 프로퍼티 이름으로 사용된다.

let jsonString = """
{
  "name": "Toughie",
  "age": 20,
  "myCity": "Seoul"
}
"""

위 데이터를 Swift의 객체로 디코딩하면 "name", "age", "city"는 코딩키가 된다.

 

Swift에서는 코딩키를 직접 지정할 수도 있다.

struct Person: Codable {
  var name: String
  var age: Int
  var city: String

  enum CodingKeys: String, CodingKey {
    case name
    case age
    case city = "myCity"
  }
}

"myCity"라는 코딩키를 "city"라는 프로퍼티 네임으로 매핑하는 것이다.

이러한 방식을 통해 JSON 데이터와 Swift 객체 사이에서 이름으로 인한 문제를 방지하고 정확하게 디코딩 할 수 있다.

 

JSON Decode 관련 문서들

https://developer.apple.com/documentation/foundation/jsondecoder/keydecodingstrategy

 

JSONDecoder.KeyDecodingStrategy | Apple Developer Documentation

The values that determine how to decode a type’s coding keys from JSON keys.

developer.apple.com

let jsonDecoder: JSONDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
//-> 코딩키를 디코딩 할 때 스네이크케이스를 카멜케이스로 바꿔서 디코딩하겠다.

https://developer.apple.com/documentation/foundation/jsondecoder/2895189-decode

 

decode(_:from:) | Apple Developer Documentation

Returns a value of the type you specify, decoded from a JSON object.

developer.apple.com

JSON데이터 어떤 형태로 디코딩 할 것인가?
여기서 제네릭과 메타 타입이 사용된다. T.Type

let jsonDecoder: JSONDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase

do {
	let datas: [타입] = try jsonDecoder.decode([타입].self, from 데이터)
    return datas
    } catch {
    print(error.localizedDescription)
    return []
    }