[iOS] - Cocoa Touch μμ JSON λ€λ£¨κΈ°
1μ°¨ μμ : 2021.05.25
JSON = JavaScript Object Notation
π€ μ¬μ μ§μ - JSON μ΄λ?
λ¨μνκ² λ°μ΄ν°λ₯Ό νννλ λ°©λ² μ€ νλμ΄λ€. (ν΅μ λ°©λ²λ μλκ³ , νλ‘κ·Έλλ° λ¬Έλ²λ μλλ€. ν¬λ§·μ΄λ€ !!!!)
μλ²μ ν΄λΌμ΄μΈνΈ κ°μ λ°μ΄ν° κ΅νμμ μΌλ°μ μΌλ‘ λ§μ΄ μ¬μ©λλ€.
ν΄λΌμ΄μΈνΈκ° API Request λ₯Ό 보λ΄λ©΄ μλ²κ° μλ΅μΌλ‘ JSON λ°μ΄ν°λ₯Ό 보λ΄μ€λ€.
JSONμ ν¬λ§·μ μλ°μ€ν¬λ¦½νΈ κ°μ²΄ νκΈ°λ²μ λ°λ₯Έλ€
key
-value
μμ μ΄λ£¨μ΄ νννλ©°, keyλ λ¬Έμμ΄μ΄λ€. λ¬Έμμ΄μ" "
μλ°μ΄νλ₯Ό μ¬μ©νμ¬ νκΈ°νλ€.- { } μ λ΄λΆμ
key-value
λ‘ κ΅¬μ±λλ€. μ¦ Swift λμ λ리μ μ μ¬ν ꡬ쑰μ΄λ€. - JSONνμμμλ null, number, string, array, object, booleanμ μ¬μ©ν μ μλ€.
λ€μ νλ² λ§νμ§λ§ JSONμ λ°μ΄ν° νν λ°©μμΌ λΏμ΄λ©°, ν μ€νΈνμμ λ°μ΄ν° μ΄λ€.
κ·Έλ κΈ° λλ¬Έμ κ°λ μ±μ΄ μ’μΌλ©°, λ¨μν ν μ€νΈμ΄κΈ° λλ¬Έμ νΉμ μΈμ΄μ μ’ μλμ§ μλλ€.
λλΆλΆμ νλ‘κ·Έλλ° μΈμ΄μμ JSON ν¬λ§· λ°μ΄ν°λ₯Ό νΈλ€λ§ ν μ μλ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ 곡νλ€
π€μ¬μ μ§μ - JSON μ΄μ μ μ¬μ©νλ XMLμ?
νΉμ§ λ°μ΄ν° κ° μμͺ½μΌλ‘ νκ·Έκ° μλ€. νκ·Έλ‘ ννλλ λ°μ΄ν°μ΄λ€.
μ΄κ²μ HTMLμ κΈ°λ°μΌλ‘ κ³ μλμκΈ° λλ¬ΈμΈλ°, νκ·Έλ₯Ό μ€μΈλ€ ν΄λ μ΅μν νννλ €λ©΄ μμͺ½μ λͺ κΈμμ©μ΄ μμ΄μΌ νκΈ° λλ¬Έμ λ΄κ° λ΄£μ λ μμ±λ μ΄λ ΅κ³ κ°λ μ±λ μ’μ§ μλ€.
μ°Έκ³ μλ£ : https://velog.io/@surim014/JSON
κ°μ
λ€νΈμν¬ μ°κ²°μ ν΅ν λ°μ΄ν° μ μ‘μ΄λ, λμ€ν¬μ λ°μ΄ν°λ₯Ό μ μ₯νκ±°λ, API μλ²λ μλΉμ€λ‘ λ°μ΄ν°λ₯Ό μ μΆνλ κ²κ³Ό κ΄λ ¨λ νλ‘κ·Έλλ° μμ λ€μ΄ λ§μ΅λλ€.
μ΄λ° μμ λ€μ μ€κ°μ μν μ ν μ μλ νμμΌλ‘μ λ°μ΄ν°μ μΈμ½λ© κ³Ό λμ½λ©μ νμλ‘ ν©λλ€.
μλλ©΄ μ€μννΈλ‘ ννλ λ°μ΄ν°λ₯Ό λ€λ₯Έ νκ²½μμλ ν΄μνκΈ° μ΄λ ΅κΈ° λλ¬Έμ λλ€.
Swift standard library λ λ°μ΄ν° μΈμ½λ© κ³Ό λμ½λ©μ νμ€νλ λ°©λ²μ μ΄λ―Έ μ μν΄λμμ΅λλ€.
μ°λ¦¬λ νμν 컀μ€ν νμ μ μ΄ λ°©λ²μ μ μ©ν κ²μΈλ°, μ΄κ²μ Encodable κ³Ό Decodable νλ‘ν μ½μ ꡬνμ ν΅ν΄μ κ°λ₯ν©λλ€.
μΌλ¨ μ νλ‘ν μ½μ ꡬν νλ©΄ Encoder μ Decoder κ° λ°μ΄ν°λ₯Ό κ°μ§κ³ JSON μ΄λ property list μ κ°μ external representation* μΌλ‘ μΈμ½λ© & λμ½λ©μ ν΄μ€λλ€.
π‘μ€μννΈ μΈλΆμμ μ¬μ©λλ λ°μ΄ν° νμμ external representation μ΄λΌκ³ νννμμ΅λλ€.
Encodable κ³Ό Decodable μ λμμ μ±ννλ€λ©΄ μΈμ½λ©κ³Ό λμ½λ© μμ λͺ¨λλ₯Ό ν μ μμ΅λλ€.
κ·Έλ¦¬κ³ κ·Έκ²μ protocol composition(&) μ ν΅νμ¬ Codable λ‘ μ μλμ΄ μμ΅λλ€.
typealias Codable = Decodable & Encodable // μ€μ λ‘ μ΄λ κ² κ΅¬νλμ΄ μμ π
λ³Έλ‘ μΌλ‘ λμμμ...
JSON Encoding κ³Ό Decoding μ νλ 주체λ JSONEncoder
μ JSONDecoder
μ€λΈμ νΈμ
λλ€.
Encoding κ³Ό Decoding μ΄ κ°λ₯νλ €λ©΄ λμ struct λEncodable
,Decodable
νλ‘ν μ½μ μ±ννκ³ μμ΄μΌ ν©λλ€.
λ§μ½ Encodable κ³Ό Decodable μ λͺ¨λ μ±ννλ€λ©΄, Codable νλ‘ν μ½μ μ±ννλ€κ³ λͺ μνλ©΄ λ©λλ€.
μ΄λ€ 컀μ€ν νμ μ codable νκ² λ§λλ κ°μ₯ μ¬μ΄ λ°©λ²μ, κ·Έ νμ μ΄ κ°μ§ μμ±λ€μ λͺ¨λ Codable νμ μΌλ‘ μ μνλ κ²μ λλ€.
Codable ν νμ μ standart library νμ μΈ String, Int, Double νΉμ Foundation νμ μΈ Date, Data, URL λ±μ΄ μμ΅λλ€.
μ΄λ° νμ λ€μ μμ±μΌλ‘λ§ κ΅¬μ±λ νμ μ Codable νλ‘ν μ½μ λ°λ₯Έλ€κ³ λͺ μλ§νλ©΄ λ©λλ€.
"automatically conforms to Codable just declaring that conformance."
struct Landmark: Codable {
var name: String
var foundingYear: Int
// μμ±λ€μ΄ μ΄λ―Έ Codable νλ―λ‘, νλ‘ν μ½ κ΅¬νμ΄ νμ μμ΅λλ€.
var location: Coordinate
// λ§μ½ 컀μ€ν
νμ
(Coordinate)μ΄ μΆκ°λλ€κ³ νλλΌλ, κ·Έ νμ
μ Codableνκ² μ μνλ€λ©΄ λ¬Έμ μμ΅λλ€.
var metadata: [String: String]
// Array, Dictionay, Optional κ°μ Built-in typeμ κ·Έ λ΄λΆ νμ
μ΄ Codable μΌ κ²½μ° Codableμ΄ λ©λλ€.
}
let encoder = JSONEncoder()
encoder.outFormatting = .prettyPrinted
encoder.keyEncodingStrategy = .converToSnakeCase
do {
// 1
let jsonData = try encoer.encode(someStruct)
// 2
if let str = String(data: jsonData, encoding: .utf8) {
print( str )
}
} catch {
print(error)
}
1 : encode() λ Throwing Method μ΄λ―λ‘ try λ‘ μ²λ¦¬ν΄μ£Όμμ΅λλ€.
κ·Έλ¦¬κ³ try λ do catch λ‘ μ²λ¦¬ν©λλ€.
λ°ν κ°μ λ°μ΄λ리 λ°μ΄ν° ννμ JSON μ λλ€.
2 :
λ°μ΄λ리 λ°μ΄ν°λ μΈκ°μ΄ μ½μ μ μκΈ° λλ¬Έμ μΈμ½λ©μ΄ νμν©λλ€.
λμΌλ‘ νμΈνλ €λ©΄ String(data:encoding:) μμ±μλ‘ λ¬Έμμ΄ μΈμ€ν΄μ€λ‘ μμ±ν΄ μΆλ ₯ν΄λ³Ό μ μμ΅λλ€.
λν outFormatting
μμ±μ .prettyPrinted
λ‘ λ³κ²½ν΄ key-value κ° κ°νμ μΆκ°νμ¬ κ°λ
μ±μ λμΌ μ μλ€.
JSON Encoding
Encoding μ Struct μΈμ€ν΄μ€μμ JSON λ°μ΄ν°λ‘ λ³ννλ κ³Όμ μ λλ€.
μμ± μ΄λ¦κ³Ό key μ΄λ¦ λ§€μΉ
JSON μμ ν€ λ€μ΄λ° 컨벀μ
μ snake_case
μ΄λ€.
νμ§λ§ Swift μμλ lowerCamelCase
λ₯Ό μ¬μ©νκΈ° λλ¬Έμ μμ μμ΄ Encoding μ μ§ννλ€λ©΄ key name μ΄ lowerCamelCase λ‘ λ€μ΄κ°κΈ° λλ¬Έμ νΈνμ±μ΄ λ¨μ΄μ§ κ²μ΄λ€.
μ΄λλ keyEncodingStrategy
μμ±μ λ³κ²½νμ¬ λμν μ μλ€.
encoder.keyEncodignStrategy = .convertToSnakeCase
λ μ§ νμ λ§€μΉ
Swift λ Date νμ μ μμ±μ JSON μΌλ‘ μΈμ½λ© ν λ, Double κ°μΌλ‘ μΈμ½λ©νλ€.
μ΄ κ°μ 2001λ 1μ 1μΌμ μμμ μΌλ‘ λ¨μ΄μ§ λ§νΌμ μκ°μ νννλλ°, μ΄ λ¨μμ΄λ€.
κ°λ μ±μ΄ λ§€μ° λ¨μ΄μ§λ―λ‘ λ§μ΄ μ¬μ©νλ iso8601 νμμ λ°λ₯Έ λ¬Έμμ΄λ‘ μΈμ½λ©νκ² λ°κΏμ€ μ μλ€.
μΈμ½λ μ€λΈμ νΈμ dateEncodingStrategy
μμ±μ λ³κ²½νλ©΄ λλ€.
encoder.dateEncodingStrategy = .iso8601
κ·Έλ°λ° μ΄ νμμ κ·Έλλ‘ λ°λ₯΄μ§ μλ API λ€μ΄ μλ€. (μ: Kakao REST API)
μ΄ κ²½μ°μ ν΄λΉ Key νΉμ μμ±μ Encoding νΉμ Decoding μ μ€ν¨νκ² λλ€.
μ΄λλ DateFormatter μΈμ€ν΄μ€λ₯Ό μ§μ λ§λ€μ΄μ encoder μ μ λ¬ν΄μΌνλ€.
// 1. Custom DateFormatter λ₯Ό μμ±νλ€.
var dateFormatter: DateFormatter = {
var formatter = DateFormatter()
formatter.dateFormat = "MMMyyyy" // dateFormat νμλ¬Έμμ΄
}()
// 2. encoder μ€λΈμ νΈμ .formmated caseμ μ°κ΄κ°μΌλ‘ μ λ¬νλ€.
encoder.dateEncodingStrategy = .formmatted(dateFormatter)
π― κ·Έλ°λ° μνλ DateFormat νμλ¬Έμμ΄μ μ§μ μμ±νλ κ²μ΄ μ΄λ ΅κ² λκ»΄μ§μ μλ€.
μ΄ λλ https://nsdateformatter.com μμ μνλ νμλ¬Έμμ΄μ κ°νΈνκ² μμ±ν μ μλ€.
JSON Decoding
Decodingμ μλ²λ‘λΆν° μ λ¬λ°μ JSON λ°μ΄ν°λ₯Ό Struct μΈμ€ν΄μ€λ‘ λ³ννλ κ³Όμ μ΄λ€.
guard let jsonData = jsonStr.data(using: .utf8) else {
fatalError()
}
//
let decoder = JSONDecoder()
let result = try? decoder.decode(Person.self, from: jsonData)
Struct μμ±μ νμ
μΌλ° νμ μ΄λΌλ©΄ decoding μ€ν¨ μ λ°νμ μ€λ₯κ° λ°μνλ€
μ΅μ λ νμ μ΄λΌλ©΄ decoding μ€ν¨ μ nil λ‘ μ΄κΈ°νλλ€.
JSON key μ struct μμ±μ΄ λ€λ₯Έ κ²½μ°
λ§μ½ ν€ λ€μκ³Ό μμ± μ΄λ¦μ Naming convention λ§ λ€λ₯΄λ€λ©΄ encoding λμ κ°μ λ°©μμΌλ‘ ν΄κ²°ν΄μ£Όλ©΄ λλ€.
decoder.keyDecodingStrategy = .convertFromSnakeCase
νμ§λ§ μ΄λ¦μ΄ μμ λ€λ₯Έκ²½μ°λ custom key mappping μ ν΄μ£Όμ΄μΌ νλ€.
CodingKey μ λν λ΄μ©μ λ°λ‘ κΈλ‘ μ 리νμμ΅λλ€
struct iPhone: Codable {
var SerialNumber: String
var Storage: Int
var generation: Int
var ramSize: Int
enum CodingKeys: String, CodingKey {
case SerialNumber
case Storage
case generation = "model_number"
case ramSize
}
}
μλ₯Ό λ€μ΄ JSON λ°μ΄ν°μμ model_number
ν€μ μ μ₯λ κ°μ generation
μμ±μ λ°μμ€κ³ μΆμ λ, custom Key mapping μ νμ§ μμΌλ©΄ decoding error κ° λ°μν κ²μ΄λ€. Int νμ
μΈ generation
μ λ§€νλ κ°μ΄ μκΈ° λλ¬Έμ΄λ€.
μμ μ½λμ κ°μ΄ νμ λ΄μ enum μ μ μΈνκ³ , raw Value λ‘ String λ‘ μ μΈνκ³ , CodingKey νλ‘ν μ½μ μ±ννλ€μ λ§€ννκ³ μΆμ case λ§ rawValue μ€μ μ ν΄μ£Όλ©΄ ν΄κ²°λλ€. rawValue μ λ€μ΄κ°λ κ°μ λ§€ννκ³ μΆμ JSON μ key name μ΄λ€.
π― Custom Encoding & Custom Decoding
λ³΄ν΅ μΈμ½λ©κ³Ό λμ½λ©μ μ μ½μ λ£κ³ μΆμλ 컀μ€ν μΌλ‘ ꡬννλ€.
Encodable & Decodable νλ‘ν μ½μ λ©μλλ₯Ό μ§μ ꡬννλ©΄ λλ€.
" π₯³ μ리λ₯Ό μ λλ‘ μ΄ν΄νμΌλ, 컀μ€ν μ΄ νμν λ λ΄μ©μ λ μΆκ°νμ! "
π‘ κΈ°λ³Έ μ리: protocol method λ₯Ό μ§μ ꡬννκ³ , container μ unkeyed-data λ₯Ό μ μ νκ² encode/decode νμ!!!!
Encodable μ κ²½μ° encode(to encoder:) Decodable μ κ²½μ° init(from decoder:) λ©μλλ₯Ό μ§μ ꡬννλ©΄ λλ€.
func encode(to encoder: Encoder) throws {
<#code#>
}
init(from decoder: Decoder) throws {
<#code#>
}
μ°μ 컀μ€ν μΈμ½λ©μ νλ €λ©΄ CodingKey νλ‘ν μ½μ μ±νν CodingKeys μ΄κ±°νμ μμ±ν΄μΌ νλ€.
struct Person: Codable {
var firstName: String
var lastName: String
var age: Int
var address: String?
enum CodingKeys: String, CodingKey {
case firstName
case lastName
case age
case address
}
μ? container(keyedBy:) μ νλΌλ―Έν°λ‘ CodingKey μ λ©ν νμ μ λ°λλ€.
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(firstName, forKey: .firstName)
try container.encode(lastName, forKey: .lastName)
guard (30...60).contains(age) else { // μ μ½μ μΆκ°νκ³ μ‘°κ±΄μ λ§μ§ μλλ€λ©΄ μλ¬λ₯Ό λμ§λ€.
throw DecodingError.invalidRange
}
try container.encodeIfPresent(address, forKey: .address)
}
컨ν μ΄λμ μ μ : " an container appropriate for holding multiple unkeyed values."
κ·Έλ¦¬κ³ encode(_, forKey:)
λ©μλλ‘ container λ΄λΆμ λ°μ΄ν°(unkeyed values)λ€μ μΈμ½λ©νμ¬ ν€μ λ§€νν΄μ€λ€.
encode λ throws
ν€μλμμ λ³Όμ μλ―μ΄ Throwing Method λ‘, λ΄λΆ λΈλμμ μλ¬λ₯Ό λμ§ μ μλ€.
μ μ½λμμλ age
μμ±μ κ°μ΄ 30~60 μ¬μ΄κ° μλλΌλ©΄ 미리 μ μλ μλ¬λ₯Ό λμ§κ³ μλ€.
λ!
π€π’[μ°μ§±μ iOS λΈλ‘κ·Έ]π΅π»
iOSλ₯Ό 곡λΆνλ©΄μ λ°°μ΄ λ΄μ©μ κΈ°λ‘νκ³ μμ΅λλ€.
μ°Έκ³ μλ£: https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding