JSON을 활용한 커스텀 타입
Swift에서 JSON형식의 데이터를 다루기위해 encoding과 decoding을 사용할때
우리는 JSONDecoder와 JSONEncoder클래스로부터 제공되는 형식을 사용해야만합니다.
그렇다면 Encoding과 Decoding은 무엇일까요?
데이터를 다루는 모델을 Swift코드로 작성할 때
Codable
프로토콜을 따르는 타입을 작성하여 JSON 데이터와 그 데이터를 Swift에서 표현하는 방법간에 변환법을 알아보겠습니다.
배열로부터 데이터읽기
만약 JSON데이터의 요소들이 동일한 타입의 배열들일때 배열의 타입은 Codable
프로토콜을 따라야합니다.
전체 배열을 decode하거나 encode할때는 [Element].self
를 사용해야합니다.
아래의 예시에 있는 배열은 Codable
프로토콜을 따르므로 자동으로 decodable합니다.
import Foundation
let json = """
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange",
"points": 100
}
]
""".data(using: .utf8)!
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)
print("The following products are available:")
for product in products {
print("\t\(product.name) (\(product.points) points)")
if let description = product.description {
print("\t\t\(description)")
}
}
Key 이름 변경
우리가 작성한 JSON을 활용하기위한 Swift모델의 데이터이름(Key값)과 실제 JSON데이터의 Key값이 다를수도 있습니다.
Codable
, Decodable
, Encodable
을 따르는 타입안에 CodingKey
를 따르는 열거형을 선언해줌으로써 JSON파일의 데이터 Key값과
Swift코드에서 다르게 쓰이는 동일한 데이터의 Key값을 매핑시켜줄 수 있습니다.
아래의 코드에서는 JSON의 “product_name” 은 name으로 “product_cost”는 points로 사용할 수 있는 코드입니다.
import Foundation
let json = """
[
{
"product_name": "Bananas",
"product_cost": 200,
"description": "A banana grown in Ecuador."
},
{
"product_name": "Oranges",
"product_cost": 100,
"description": "A juicy orange."
}
]
""".data(using: .utf8)!
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
private enum CodingKeys: String, CodingKey {
case name = "product_name"
case points = "product_cost"
case description
}
}
let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)
print("The following products are available:")
for product in products {
print("\t\(product.name) (\(product.points) points)")
if let description = product.description {
print("\t\t\(description)")
}
}
“description”의 경우 양쪽에서 모두 같은 이름으로 쓰이지만 GroceryProduct의 요소이므로 열거형안에도 명시를 해줍니다.
또한 양쪽에서 같은 이름으로 사용하므로 위의 name이나 points와 달리 raw value
를 지정해주지 않아도 괜찮습니다.
Nested Data 접근
앱을 설계할때 우리는 JSON데이터를 외부에서 가져와 사용할 수도 있고 로컬에 있는 JSON데이터를 사용할 수도 있습니다.
이런 두 경우에서 모두 우리가 작성한 JSON데이터를 사용하기위한 모델과 실제 JSON데이터간의 차이는 존재할 수 있습니다.
떄떄로 Swift에서 사용하는 논리적 데이터의 묶음은 여러개의 객체나 배열로 JSON데이터에 군집화되어 있습니다.
이런 구조적인 차이를 decodable타입을 매개체로 활용하여 안전하게 decode시킬 수 있습니다.
외부에서 전달되는 JSON 데이터를 이런 일련의 과정들을 initializer로 활용하여 우리의 앱에 사용할 수 있게됩니다.
아래의 코드는 grocery store과 물품의 목록을 나타내는 코드입니다.
import Foundation
struct GroceryStore {
var name: String
var products: [Product]
struct Product: Codable {
var name: String
var points: Int
var description: String?
}
}
아래의 코드에 작성되어있는 JSON데이터의 정보를 JSON데이터 활용 API는 담을 수 있어야합니다!
let json = """
[
{
"name": "Home Town Market",
"aisles": [
{
"name": "Produce",
"shelves": [
{
"name": "Discount Produce",
"product": {
"name": "Banana",
"points": 200,
"description": "A banana that's perfectly ripe."
}
}
]
}
]
},
{
"name": "Big City Market",
"aisles": [
{
"name": "Sale Aisle",
"shelves": [
{
"name": "Seasonal Sale",
"product": {
"name": "Chestnuts",
"points": 700,
"description": "Chestnuts that were roasted over an open fire."
}
},
{
"name": "Last Season's Clearance",
"product": {
"name": "Pumpkin Seeds",
"points": 400,
"description": "Seeds harvested from a pumpkin."
}
}
]
}
]
}
]
""".data(using: .utf8)!
API에서 반환된 JSON데이터에는 Swift 타입에 명시되어있는 것보다 더 많은 정보를 담고있습니다.