Reference
- 내용전반: Apple문서
decode()
의 parameter로 배열을 주면 인스턴스 배열이 만들어집니다
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)")
}
}
CodingKeys
열거형을 정의하여 코드에서 사용할 프로퍼티명이 Swift API Design Guidelines를 준수하도록 변경해줄 수 있습니다
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)")
}
}
코드만 봐도 이해가능
struct GroceryStoreService: Decodable {
let name: String
let aisles: [Aisle]
struct Aisle: Decodable {
let name: String
let shelves: [Shelf]
struct Shelf: Decodable {
let name: String
let product: GroceryStore.Product
}
}
}
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)!
let decoder = JSONDecoder()
let serviceStores = try decoder.decode([GroceryStoreService].self, from: json)
let stores = serviceStores.map { GroceryStore(from: $0) }
for store in stores {
print("\(store.name) is selling:")
for product in store.products {
print("\t\(product.name) (\(product.points) points)")
if let description = product.description {
print("\t\t\(description)")
}
}
}
아래 JSON에서 Banana와 points
/description
은 depth가 다르다
import Foundation
let json = """
{
"Banana": {
"points": 200,
"description": "A banana grown in Ecuador."
},
"Orange": {
"points": 100
}
}
""".data(using: .utf8)!
인코딩/디코딩 로직을 직접 구현함으로써
이들을 병합하여 동일한 Depth로 만들어줄 수도 있다
예시로, Banana를 name
이라는 프로퍼티로 만들고
name
/points
/description
3개 프로퍼티를 가지는 Product
타입으로 디코딩해보자
JSONDecoder가 가진 container
등의 메서드를 추가로 활용한다
struct GroceryStore {
struct Product {
let name: String
let points: Int
let description: String?
}
var products: [Product]
}
extension GroceryStore: Decodable {
struct ProductKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
static let points = ProductKey(stringValue: "points")!
static let description = ProductKey(stringValue: "description")!
}
public init(from decoder: Decoder) throws {
var products = [Product]()
let container = try decoder.container(keyedBy: ProductKey.self)
for key in container.allKeys {
// Note how the `key` in the loop above is used immediately to access a nested container.
let productContainer = try container.nestedContainer(keyedBy: ProductKey.self, forKey: key)
let points = try productContainer.decode(Int.self, forKey: .points)
let description = try productContainer.decodeIfPresent(String.self, forKey: .description)
// The key is used again here and completes the collapse of the nesting that existed in the JSON representation.
let product = Product(name: key.stringValue, points: points, description: description)
products.append(product)
}
self.init(products: products)
}
}
let decoder = JSONDecoder()
let decodedStore = try decoder.decode(GroceryStore.self, from: json)
print("The store is selling the following products:")
for product in decodedStore.products {
print("\t\(product.name) (\(product.points) points)")
if let description = product.description {
print("\t\t\(description)")
}
}
상속관계를 가지는 class에서 subclass의 인스턴스를 superclass로 타입 캐스팅하더라도 '겉모습은 superclass이면서 실체는 subclass를 유지하는' 다형성을 보입니다
하지만 아래와 같이 Codable 프로토콜로 Encoding/Decoding하는 경우에는 다형성을 가질 수 없는 문제가 있습니다
class AAA: Codable {
let aaa: Int = 1
var description: String {
return "AAA"
}
}
class BBB: AAA {
let bbb: Int = 2
override var description: String {
return "BBB"
}
}
class CCC: BBB {
let ccc: Int = 3
override var description: String {
return "CCC"
}
}
do{
let encoded = try JSONEncoder().encode(CCC())
let decoded = try JSONDecoder().decode(AAA.self, from: encoded)
print(decoded.description)
// print AAA (?!?)
} catch {
print(error)
}
NSCoding 프로토콜을 사용하는 방법은 YodaCodd님 포스팅을 참고바랍니다