Do.

Swift - JSON Encoding과 Decoding - Part1 본문

iOS

Swift - JSON Encoding과 Decoding - Part1

Hey_Hen 2022. 2. 9. 15:57

 

만드는 어플에 JSON 데이터를 파싱해야 하는데 이게 눈으로만 보고 할려고 하니까 너무 어려워서 정리하면서 공부 할려고 한다.

레퍼런스 : https://www.raywenderlich.com/3418439-encoding-and-decoding-in-swift

이미지 썸네일 삭제
Encoding and Decoding in Swift

In this tutorial, you’ll learn all about encoding and decoding in Swift, exploring the basics and advanced topics like custom dates and custom encoding.

www.raywenderlich.com

 

목적은 원하는 JSON 데이터를 Decoding 하는 것이 주 목적이다.

 

우선 간략하게 객체와 인코딩에 대해서 가볍게 살펴보면

인코딩

struct Person: Codable {
  let name: String
  let age: String
  let gender: String
}

let originalData = Person(name: "henry", age: "28", gender: "male")
let encodeData = try JSONEncoder().encode(originalData)

print(String(data: encodeData, encoding: .utf8)!)
 
{"name":"henry","age":"28","gender":"male"}
 

1. JSON data로 인코딩 하고자 하는 객체 Person은 Codable 프로토콜을 채용한다. Codable는 Encodable과 Decodable 모두 채용하는 일종의 별칭이다.

 

따라서 위와같이 Encoding만 하자면 Person이 Codable일 필요까진 없고, Encodable만 채용해도 된다.

 

2. originalData라는 이름으로 Person 을 생성했다.

3. encodeData는 originalData를 JSON형태로 인코딩 할 내용으로

해당 내용은

let encoder = JSONEncoder()
let encodeData = try encoder.encode(originalData)
 

와 같은데 JSONEncoder()를 별도로 변수 또는 상수로 선언하게 되면 각종 옵션을 사용할 수 있다.

아까처럼 JSONEncoder().encode 한 후 바로 출력하게 되면

{"name":"henry","age":"28","gender":"male"}
 

위와같이 출력되게 되는데 outputFormatting 프로퍼티를 설정하면

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encodeData = try encoder.encode(originalData)
 
{
  "name" : "henry",
  "age" : "28",
  "gender" : "male"
}
 

보기쉽게 계층화 되어서 보여준다.

 

디코딩

이제 디코딩을 얘기하자면 JSON으로 인코딩된 데이터를 다시 객체로 돌리는 방법으로 보면 된다.

코드를 싹 지우고

아까 인코딩 된 데이터만 남아있다고 보자

let encodeData = try encoder.encode(originalData)
 

아까 인코딩 된 녀석인데 이는

let data =
  """
  {
    "name": "henry",
    "age": "28",
    "gender": "male"
  }
  """
  .data(using: .utf8)!
 

이거랑 동일하다고 봐도 된다.

그래서 아래 처럼 써줄 것이다.

struct Person: Decodable {
  let name: String
  let age: String
  let gender: String
}

let data =
  """
  {
    "name": "henry",
    "age": "28",
    "gender": "male"
  }
  """
  .data(using: .utf8)!

let decoder = JSONDecoder()
let person = try decoder.decode(Person.self, from: data)
 

1. 똑같은 구조체가 있고 (이번에는 Decodable, Codable 이어도 상관없다.)

2. data는 아까 인코딩 된 데이터이다.

3. 순서는 encoding과 동일하다 decode메소드의 파라메터는 타입과 디코딩할 데이터를 받는다.

 

대표사진 삭제

사진 설명을 입력하세요.

REST API를 통해 JSON데이터를 가져왔을 때 이런식으로 미리 지정해놓은 객체로 데이터를 전달할 수 있다.

 

예를 출력해보면

let mirror = Mirror(reflecting: person)
for child in mirror.children {
  print("\(child.label ?? "") : \(child.value)")
}
 
name : henry
age : 28
gender : male
 

data 그래도 출력이 되었다.

물록 객체로 만들었기 때문에

person.name
person.age
person.gender
 

이런식으로 접근이 가능하다.

 

또 이 부분도 중요한데 JSONDecoder의 편리한 점은 원하는 부분만 받을 수 있다.

{
  "name" : "henry",
  "age" : "28",
  "gender" : "male"
}
 

위와 같이 똑같은 데이터를 REST를 통해 JSON으로 받았다고 쳤을 때 만들고자 하는 앱에서 gander 프로퍼티는 딱히 필요가 없어서 받을 생각이 없다.

 

받을 객체를 생성할 때 받을 생각이 없는 데이터 프로퍼티를 굳이 생성할 필요가 없다.

struct Person: Decodable {
  let name: String
  let age: String
//  let gender: String
}
 
name : henry
age : 28
 

이렇게 gender 항목은 쏙 빼고 가져온다. 실제로 REST API로 받으면 데이터가 엄청 많은 경우가 있는데 사용하지 않을 데이터 까지 일일이 받을 필요는 없다는 뜻이다.

 

Nested Type

별도로 문서로 정리하고자 생각했던것은 JSON 데이터가 단순하지 않아서 각 상황마다 어떻게 받아야 할지 몰랐기 때문이다.

 

Nested Type은 말 그대로 타입 내부에 타입이 존재하는 경우다.

 

{
  "name" : "henry",
  "age" : "28",
  "gender" : "male"
  "job" : {
    "name" : "developer"
  }
}
 

Person이 Job이라는 객체를 가지고 있다. 위 데이터를 인코딩 했다고 치면 아래와 같이 쓸 수 있겠다.

import Foundation

struct Person: Codable {
  var name: String
  var age: String
  var gender: String
  var job: Job
}

struct Job: Codable {
  var name: String
}

let henrysJob = Job(name: "developer")
let henry = Person(name: "henry", age: "28", gender: "male", job: henrysJob)
//same let henry = Person(name: "henry", age: "28", gender: "male", job: Job(name: "developer"))

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encodingData = try encoder.encode(henry)

print(String(data: encodingData, encoding: .utf8)!)
 
{
  "name" : "henry",
  "age" : "28",
  "gender" : "male",
  "job" : {
    "name" : "developer"
  }
}
 

위 json 데이터에서 job 안의 데이터를 Job 객체로 표현했다.

 

이를 다시 Decode 하자면

let decoder = JSONDecoder()
let decodingData = try decoder.decode(Person.self, from: encodingData)

decodingData.job.name // developer
 

Simple 😁

마찬가지로 만약 job 프로퍼티를 받고 싶지 않다면 사전에 설정한 Person객체에서 job을 지우면 된다

 

import Foundation

struct Person: Codable {
  var name: String
  var age: String
  var gender: String
  var job: Job
}

struct Job: Codable {
  var name: String
  var category: JobCategory
}

struct JobCategory: Codable {
  var cat1: String
  var cat2: String
  var cat3: String
}

let henrysJobCategory = JobCategory(cat1: "Computer/IT", cat2: "SoftwareDeveloper", cat3: "iOSDeveloer")
let henrysJob = Job(name: "developer", category: henrysJobCategory)
let henry = Person(name: "henry", age: "28", gender: "male", job: henrysJob)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encodingData = try encoder.encode(henry)

print(String(data: encodingData, encoding: .utf8)!)

let decoder = JSONDecoder()
let decodingData = try decoder.decode(Person.self, from: encodingData)

let mirror = Mirror(reflecting: decodingData.job.category)

mirror.children.forEach {
  print("\($0.value)",terminator: "->")
}
 
{
  "age" : "28",
  "gender" : "male",
  "name" : "henry",
  "job" : {
    "name" : "developer",
    "category" : {
      "cat1" : "Computer\/IT",
      "cat3" : "iOSDeveloer",
      "cat2" : "SoftwareDeveloper"
    }
  }
}
Computer/IT->SoftwareDeveloper->iOSDeveloer->
 

이런식으로 다중으로 Nested 상태여도 전혀 문제없다.

 

CamelCase, SnakeCase 컨버팅

Swift에서 제공하는 JSONEncoder, JSONDecoder의 편리한 점은 네이밍 기법을 자동으로 컨버팅 해준다는 부분도 있다.

제공하는 REST API가 혹시나 Snake Case로 데이터를 제공한다면 이를 Camel Case로 컨버팅이 가능하다.

import Foundation

struct Person: Codable {
  var name: String
  var age: String
  var gender: String
  var majorJob: Job
}

struct Job: Codable {
  var name: String
  var category: JobCategory
}

struct JobCategory: Codable {
  var cat1: String
  var cat2: String
  var cat3: String
}

let henrysJobCategory = JobCategory(cat1: "Computer/IT", cat2: "SoftwareDeveloper", cat3: "iOSDeveloer")
let henrysJob = Job(name: "developer", category: henrysJobCategory)
let henry = Person(name: "henry", age: "28", gender: "male", majorJob: henrysJob)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.keyEncodingStrategy = .convertToSnakeCase

let encodingData = try encoder.encode(henry)

print(String(data: encodingData, encoding: .utf8)!)
 

원래 Person의 job 속성이름을 majorJob으로 CamelCase로 바꾸었는데 이를 인코딩시 .keyEncodingStrategy = .convertToSnakeCase 를 통해서 snake case로 제공 가능하다.

{
  "age" : "28",
  "gender" : "male",
  "major_job" : {
    "name" : "developer",
    "category" : {
      "cat1" : "Computer\/IT",
      "cat3" : "iOSDeveloer",
      "cat2" : "SoftwareDeveloper"
    }
  },
  "name" : "henry"
}
 

반대로 가져와야 하는 데이터가 위와 같이 snake case이고 개발중인 프로그램에서 camel case를 쓴다면

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decodingData = try decoder.decode(Person.self, from: encodingData)
 

줄 한줄 추가하는 것으로 간편하게 camelCase로 변경 가능하다.

 

Array

속성중에 배열이 섞여 있는 경우도 있는데 똑같이 처리하면 문제가 없다.

import Foundation

struct Person: Codable {
  var name: String
  var age: String
  var gender: String
  var majorJob: Job
}

struct Job: Codable {
  var name: String
  var categories: [JobCategory]
}

struct JobCategory: Codable {
  var category: String
}

let henrysJobCategory = [JobCategory(category: "Computer"), JobCategory(category: "IT"), JobCategory(category: "SoftwareDeveloper"), JobCategory(category: "iOSDeveloper")]
let henrysJob = Job(name: "developer", categories: henrysJobCategory)
let henry = Person(name: "henry", age: "28", gender: "male", majorJob: henrysJob)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted

let encodingData = try encoder.encode(henry)

print(String(data: encodingData, encoding: .utf8)!)

let decoder = JSONDecoder()
let decodingData = try decoder.decode(Person.self, from: encodingData)

decodingData.majorJob.categories.forEach { job in
  print(job.category)
}
 

Job의 Category가 categories 배열로 바뀌었다

인코딩 된 데이터는

{
  "age" : "28",
  "gender" : "male",
  "majorJob" : {
    "name" : "developer",
    "categories" : [
      {
        "category" : "Computer"
      },
      {
        "category" : "IT"
      },
      {
        "category" : "SoftwareDeveloper"
      },
      {
        "category" : "iOSDeveloper"
      }
    ]
  },
  "name" : "henry"
}
 

 

Part1 정리

1. 데이터를 JSON으로 인코딩, 디코딩 하는 법

2. NestedType을 인코딩, 디코딩 하는 법

3. 배열 타입 인코딩, 디코딩 하는 법

 

Comments