티스토리 뷰

Swift에서 JSON Parsing하기 - JSONSerialization vs Codable

해당 내용은 회사에서 전사 세미나를 준비한 내용입니다.
오류가 있거나 수정되어야 할 내용이 있다면 언제든지 알려주세요.

저는 현재 챗봇 빌더 파트에서 iOS Client 개발을 담당하고 있는데요,
개발을 진행하면서 편하게 사용했던 Swift의 Codable에 대해 소개 해 드리고자 합니다.

JSON

개발하고 있는 챗봇 빌더의 API 서버와 채팅 클라이언트 사이에서 사용되는 인터페이스 규격은 다음과 같습니다.

{
  "code": "1000",
  "data": {
    "contentType": [
      "card",
      "button"
    ],
    "inputType": "text",
    "responseText": [
      "카드 응답입니다."
    ],
    "responseButtons": [
      {
        "name": "블록",
        "type": "blockLink",
        "nextBlock": {
          "id": "blockId",
          "name": "blockName",
          "blockIndex": "blockIndex"
        },
        "webLinkUrl": "",
        "appLinkUrl": ""
      },
      {
        "name": "웹",
        "type": "webLink",
        "nextBlock": {},
        "webLinkUrl": "https://webLinkUrl",
        "appLinkUrl": ""
      },
      {
        "name": "앱",
        "type": "appLink",
        "webLinkUrl": "",
        "appLinkUrl": "https://appLinkUrl"
      }
    ],
    "responseTitle": "카드 응답의 헤더입니다.",
    "responseAction": {},
    "imagePath": "imagePath",
    "imageUrl": "https://imageUrl",
    "entities": [],
    "requiredEntities": []
  }
}

위의 규격을 이용하여 아래와 같은 응답을 만들어야 합니다.

따라서 오늘은 인코딩 보다는 디코딩 위주로 설명을 드리도록 하겠습니다. 인코딩은 디코딩의 반대 개념으로 진행하면 되기 때문에 쉽게 사용하실 수 있을 것 같습니다.

JSONSerialization

Swift에서 JSON을 파싱하기 위해서는 JSONSerialization을 사용합니다.

아니, 사용했었습니다.

Apple Developer 홈페이지에 들어가 보면 , JSONSerialization을 다음과 같이 표현합니다.

An Object that converts between JSON and the equivalent Foundation objects.

쉽게 말해, JSON을 Array 또는 Dictionary로, Array 또는 Dictionary를 JSON으로 바꿔주는 객체입니다.
이 때 Dictionary의 key 값은 String이어야 하고, Object 값은 String, Number, Array, Dictionary, Null 중에 하나여야 합니다.

제가 가진 JSON 객체를 원하는 객체 타입으로 - 보통 Dictionary를 사용합니다 - 바꿔주기 위해서는
JSONSerialization 안에 있는

class func jsonObject(with: Data, options: JSONSerialization.ReadingOptions) -> Any

함수를 사용해야 합니다.

with에 지정된 Data 형식의 json 객체를, options에 지정된 옵션에 따라 Any 뒤에 기술 될 형식으로 바꿔주는 함수입니다.
그러나 앞서 말씀드렸던 것처럼, Array 또는 Dictionary 형식만 사용할 수 있기 때문에 [String : Any]의 형태가 되고는 합니다.

Codable

그렇다면 Swift 4.0에서 야심차게 소개된 Codable은 무엇일까요?

마찬가지로 Apple Developer 홈페이지에서는, Codable을 아래와 같이 표현합니다.

A type that convert itself into and out of an external representation.

결론부터 말하자면, Decodable과 Encodable이라는 프로토콜을 둘 다 사용할 수 있는 타입이라고 볼 수 있습니다.
JSON 객체로, 또는 JSON 객체로부터 변환하고 싶은 형태의 구조체를 Codable이라는 프로토콜을 사용해서 만들고 JSONDecoder를 사용해서 디코딩 할 수 있습니다.

func decode<T>(T.Type, from: Data) -> T

함수가 그 역할을 하게 됩니다.

from에 지정된 Data 형식의 객체를 T의 Type으로 변환해주는 함수입니다.

사실 Codable을 사용하는 것이 훨씬 편한데, 글로만 설명하니 둘 다 너무 어려워보입니다.
그러면 실제로 어떻게 JSON을 디코딩 할 수 있는 지 확인해 보도록 하겠습니다.

Swift Server-Side FrameWork - Vapor

우선 JSON 형식으로 된 Response를 요청할 API 서버를 만들어보겠습니다.

Django에는 Python이, Javascript에는 Node.js가 있듯이 Swift에도 서버사이드 프레임워크가 있습니다.

Vapor와 Perfect, Kitura 등 다양한 프레임워크가 있지만 저는 그 중에서도 Vapor를 사용해서 서버를 만들어보겠습니다. 이유는 단순합니다. 작성일 현재(2020.06.12) Github 기준으로 가장 많은 Star를 받았기 때문이죠.

설치는 Vapor 공식 사이트를 참고하시면 됩니다.

저는 Seminar라는 이름의 프로젝트를 만들었습니다.

JSON Parsing 하기

간단한 json

우선은 vapor project에서 name과 message를 가지고 있는 간단한 형식의 json을 만들어보겠습니다.

struct SimpleJson: Content {
    var name: String
    var message: String
}
router.get("simple") { req in
    return SimpleJson(name: "hyeon", message: "Good to see you")
}

Client로 사용할 프로젝트에서 기본 제공되는 URLSession으로 통신을 진행하면
주석처리 된 부분에 데이터가 들어오게 됩니다.

if let url = URL(string: "http://localhost:8080/simple") {
  var request = URLRequest.init(url: url)

  request.httpMethod = "GET"

  URLSession.shared.dataTask(with: request) { (data, response, error) in
    guard let data = data else { return }

    // data
  }.resume()
}

simple json을 요청했을 때 name을 출력하겠습니다.

1. JSONSerialization

if let url = URL(string: "http://localhost:8080/simple") {
  var request = URLRequest.init(url: url)

  request.httpMethod = "GET"

  URLSession.shared.dataTask(with: request) { (data, response, error) in
    guard let data = data else { return }

    // data
    if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] {
      if let name = json["name"] as? String {
        print(name) // hyeon
      }
    }
  }.resume()
}

2. Codable

struct SimpleJson: Codable {
    var name: String
    var message: String
}

if let url = URL(string: "http://localhost:8080/simple") {
  var request = URLRequest.init(url: url)

  request.httpMethod = "GET"

  URLSession.shared.dataTask(with: request) { (data, response, error) in
    guard let data = data else { return }

    // data
        let decoder = JSONDecoder()
        if let json = try? decoder.decode(SimpleJson.self, from: data) {
      print(json.name) // hyeon
    }
  }.resume()
}

두 가지의 코드를 놓고 비교해보면, JSONSerialization과 Codable의 차이를 발견하기 쉽지 않습니다.

Codable에서 작성 된 코드는 struct도 작성해야 하고 좀 더 귀찮아 보입니다.
실제로 두 가지의 실행 시간을 놓고 보면 decoding 하는 부분에 있어서는
JSONSerialization의 경우 평균 0.0002초, Codable의 경우 평균 0.0004초로 약 2배의 시간이 걸립니다.

그렇다면 대체 사용도 귀찮아보이고, 시간도 오래 걸리는 Codable을 왜 쓰는 걸까요?

복잡한 json

우리가 실제로 사용하는 json은 위의 코드처럼 절대 간단하지 않기 때문입니다.

일례로 맨 처음에 실제 사용하고 있는 응답 형식을 보여드렸는데요, 그와 같은 형식에서는 Codable이 정말 매력적인 프로토콜입니다.

vapor에서 실제로 사용하고 있는 형식의 json을 만들어보겠습니다.

struct ComplexJson: Content {
    let code: String
    let data: BotData
}

struct BotData: Content {
    let contentType: [String]
    let inputType: String
    let responseText: [String]
    let responseButtons: [ResponseButton]?
    let responseTitle: String?
    let imageUrl: String?
}

struct ResponseButton: Content {
    let name: String
    let type: String
    let nextBlock: NextBlock?
    let webLinkUrl: String
    let appLinkUrl: String
}

struct NextBlock: Content {
    let id: String?
    let name: String?
    let blockIndex: String?
}
router.get("complex") { req in
        let nextBlock = NextBlock(id: "blockId", name: "blockName", blockIndex: "blockIndex")
        let responseButton1 = ResponseButton(name: "블록", type: "blockLink", nextBlock: nextBlock, webLinkUrl: "", appLinkUrl: "")
        let responseButton2 = ResponseButton(name: "웹", type: "webLink", nextBlock: NextBlock(id: nil, name: nil, blockIndex: nil), webLinkUrl: "https://webLinkUrl", appLinkUrl: "")
        let responseButton3 = ResponseButton(name: "앱", type: "appLink", nextBlock: nil, webLinkUrl: "", appLinkUrl: "https://appLinkUrl")
        let data = BotData(contentType: ["card", "button"], inputType: "text", responseText: ["카드 응답입니다."], responseButtons: [responseButton1, responseButton2, responseButton3], responseTitle: "카드 응답의 헤더입니다.", imageUrl: "https://imageUrl")
        return ComplexJson(code: "1000", data: data)
    }

router.get("complex")로 설정했기 때문에 이번에 실행할 URLSession의 형식은 다음과 같습니다.

if let url = URL(string: "http://localhost:8080/complex") {
  var request = URLRequest.init(url: url)

  request.httpMethod = "GET"

  URLSession.shared.dataTask(with: request) { (data, response, error) in
    guard let data = data else { return }

    // data
  }.resume()
}

이번에는 complex json을 요청했을 때

 

1. code의 값 출력

2. data의 contentType 출력

     2 - 1) data의 contentType에 textRandom이가 포함되어 있을 때 responseText 출력

     2 - 2) data의 contentType에 image가 포함되어 있을 때 imageUrl 출력

     2 - 3) data의 contentType에 card가 포함되어 있을 때 responesTitle, responseText, imageUrl 출력

     2 - 4) data의 contentType에 button이 포함되어 있을 때 responseButtons 출력

총 6가지의 작업을 수행해보도록 하겠습니다.

1. JSONSerialization

if let url = URL(string: "http://localhost:8080/complex") {
    var request = URLRequest.init(url: url)

    request.httpMethod = "GET"

    URLSession.shared.dataTask(with: request) { (data, response, error) in
        guard let data = data else { return }

        // data
        if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] {
            if let code = json["code"] as? String {
                print(code) // 1000
            }
            if let data = json["data"] as? [String : Any], let contentType = data["contentType"] as? [String]{
                print(contentType)
                if contentType.contains("textRandom") {
                    if let responseText = data["responseText"] as? String {
                        print(responseText)
                    }
                } else if contentType.contains("image") {
                    if let imageUrl = data["imageUrl"] as? String {
                        print(imageUrl)
                    }
                } else if contentType.contains("card") {
                    if let responseTitle = data["responseTitle"] as? String, let responseText = data["responseText"] as? [String], let imageUrl = data["imageUrl"] as? String {
                        print(responseTitle)
                        print(responseText)
                        print(imageUrl)
                    }
                }

                if contentType.contains("button") {
                    if let responseButtons = data["responseButtons"] as? [[String : Any]] {
                        print(responseButtons)
                    }
                }
            }
        }
    }.resume()
}

 

어떻게 느껴지시나요? json에 대한 code 값을 불러올 때 까지는 괜찮아보이지만 data 값 내부를 핸들링 할 때는 굉장히 복잡해 보입니다.

이는 

아까까지는 대수롭지 않게 생각했던 

Swift에서 사용하는 옵셔널 바인딩(Optional Binding) 과 함께 타입 정의가 계속해서 사용되었기 때문입니다.

 

간단하게 설명을 드리자면 Optional은 해당 값이 존재하지 않을 수 있음을 의미합니다.

var word: String? = "Hello"

위의 예제에서 word라는 변수는 String 값이지만 뒤에 ?가 붙으면서 String 값이 들어올 수도, 들어오지 않는 nil 일 수도 있는 Optional 형태가 되었습니다.

이 때 word 변수에 값이 있는 지 확인하기 위해 사용하는 것이 옵셔널 바인딩입니다.

if let word = word {
  print("word is not nil")
}

// same as this
if word != nil {
  print("word is not nil")
}

다시 본론으로 돌아가서, 본문에 사용되었던 예제를 다시 보겠습니다.

if let data = json["data"] as? [String : Any], let contentType = data["contentType"] as? [String]{
  //
}

json 안에 data를 key 값으로 가지는 value 값이 [String : Any] 의 Dictionary 형태로 존재를 한다면 data라는 변수로 저장하고,

해당 data 내부에 contentType을 key 값으로 가지는 value 값이 [String] 의 형태로 존재를 한다면 contentType이라는 변수로 저장 한다는 뜻입니다.

따라서 JSONSerialization으로 json을 핸들링 한다면, 내부 값에 대해 매번 타입을 정의하면서 하나 하나 벗겨줘야 하는 불편함이 생깁니다.

2. Codable

그렇다면, Codable을 사용해서 해당 예제를 수행 해 보겠습니다.

struct ComplexJson: Codable {
    let code: String
    let data: BotData
}

struct BotData: Codable {
    let contentType: [String]
    let inputType: String
    let responseText: [String]
    let responseButtons: [ResponseButton]?
    let responseTitle: String?
    let imageUrl: String?
}

struct ResponseButton: Codable {
    let name: String
    let type: String
    let nextBlock: NextBlock?
    let webLinkUrl: String
    let appLinkUrl: String
}

struct NextBlock: Codable {
    let id: String?
    let name: String?
    let blockIndex: String?
}

if let url = URL(string: "http://localhost:8080/complex") {
    var request = URLRequest.init(url: url)

    request.httpMethod = "GET"

    URLSession.shared.dataTask(with: request) { (data, response, error) in
        guard let data = data else { return }

        // data
        let decoder = JSONDecoder()
        if let json = try? decoder.decode(ComplexJson.self, from: data) {
            print(json.code)

            if json.data.contentType.contains("textRandom") {
                print(json.data.responseText)
            } else if json.data.contentType.contains("image") {
                if let imageUrl = json.data.imageUrl {
                    print(imageUrl)
                }
            } else if json.data.contentType.contains("card") {
                if let responseTitle = json.data.responseTitle, let imageUrl = json.data.imageUrl {
                    print(responseTitle)
                    print(json.data.responseText)
                    print(imageUrl)
                }
            }

            if json.data.contentType.contains("button") {
                if let responseButtons = json.data.responseButtons {
                    print(responseButtons)
                }
            }

        }

    }.resume()
}

Codable로 정의한 코드를 보니 간편한게 보이시나요?

위에서 구조체를 미리 정의해 두었기 때문에 타입을 정의할 필요도 없고, 일반 객체에 접근하듯이 .(dot)을 이용해서 접근할 수 있기 때문에 오타에 대한 부담도 많이 줄어들게 됩니다.

또한 지금은 단순히 해당 값을 출력하기만 했기 때문에 Codable에서만 구조체를 정의해야 하는 것 처럼 보입니다.

만약, JSONSerialization으로 작성 된 코드에서 파싱 된 json을 객체에 저장해서 다른 곳에서 사용해야 한다면 어떻게 될까요?
Codable과 마찬가지로 response에 대한 구조체를 미리 작성 해 두고 일일이 대입을 해 주어야 할 것입니다.

let complexJson = ComplexJson()

if let code = json["code"] as? String {
  complexJson.code = code
  print(code)
}

위와 같은 내용처럼 말이죠.

이 때에도 responseButtons처럼 json 안에 또 다른 json이 들어가 있는 경우에는 모든 내용물을 벗겨내고 다시 정의를 해 주어야 합니다.

var responseButtonList: [ResponseButton]

if let responseButtons = data["responseButtons"] as? [[String : Any]] {
  print(responseButtons)

  for i in 0..<responseButtons.count {
    let responseButton = ResponseButton()
    if let name = responseButtons[i]["name"] as? String {
        responseButton.name = name  
    }
    if let type = responseButtons[i]["type"] as? String {
      responseButton.type = type
    }

    // and else ...
  }
}

전부 다 끝까지 작성하지는 않았지만, ResponseButton 안에는 NextBlock이라는 객체가 또 들어가야 하기 때문에 실제 코드는 훨씬 더 길어집니다.

let json = try? decoder.decode(ComplexJson.self, from: data)

하지만 Codable에서는 이미 ComplexJson이라는 타입의 객체에 값을 할당 해 놓았기 때문에 추가적으로 작업할 필요가 없습니다.
간단한 json을 다룰 때는 느끼지 못했던 편리함이 체감되는 부분이라고 생각됩니다.

후기

이러한 부분까지 확인을 해 보니 Codable이 왜 JSONSerialization보다 편한지, 많은 사람들이 Codable을 극찬 했는지 공감하실 것 같습니다.

저 또한 이런 부분들에서 굉장한 매력을 느끼고 Codable을 사용했기 때문이죠.

사실 구조체를 정의 해 놓지 않고 출력만 한 복잡한 json의 첫 번째 예시를 보면 JSONSerialization은 0.0005초, Codable은 0.002초로 꽤나 차이가 나는 것 처럼 보입니다.

그러나 구조체를 정의 해 놓고 객체에 값을 할당까지 한 복잡한 json의 두 번째 예시를 보면 JSONSerialization은 0.01초로 실행시간이 갑자기 확 늘어나는 것을 볼 수 있습니다. (실행 시간은 class, struct 정의 시간을 제외하였습니다.)
실행 시간의 측면에 있어서도 Codable이 더 유용함을 보여줄 수 있는 부분이라고 생각합니다.

따라서, 구조가 간편한 json을 다룰 때에는 JSONSerialization이 더 편리하고 빠르지만 그 이외에는 Codable을 사용하는 것을 추천드립니다.

 

지금까지 제가 챗봇 빌더의 iOS Client를 개발하며 느꼈던 Codable의 편리함에 대해 소개 해 드렸는데요,
직접 코드를 작성해 보시면 제가 설명하지 않았던 다른 장단점까지 느끼실 수 있으리라 생각합니다.

Vapor로 작성한 서버 코드와 본문에서 사용된 전체 코드에 대해서는 아래 쪽에 별첨으로 달아두었으니 많은 의견 부탁드립니다.

감사합니다.

# 별첨1. Vapor Code

JsonObject.swift

import Vapor

struct SimpleJson: Content {
    var name: String
    var message: String
}

struct ComplexJson: Content {
    let code: String
    let data: BotData
}

struct BotData: Content {
    let contentType: [String]
    let inputType: String
    let responseText: [String]
    let responseButtons: [ResponseButton]?
    let responseTitle: String?
    let imageUrl: String?
}

struct ResponseButton: Content {
    let name: String
    let type: String
    let nextBlock: NextBlock?
    let webLinkUrl: String
    let appLinkUrl: String
}

struct NextBlock: Content {
    let id: String?
    let name: String?
    let blockIndex: String?
}

routes.swift

import Vapor

/// Register your application's routes here.
public func routes(_ router: Router) throws {
    // Basic "It works" example
    router.get { req in
        return "It works!"
    }

    // Basic "Hello, world!" example
    router.get("hello") { req in
        return "Hello, world!"
    }

    router.get("simple") { req in
        return SimpleJson(name: "hyeon", message: "good to see you")
    }

    router.get("complex") { req in
        return ComplexJson(code: "1000", data: BotData(contentType: ["card", "button"], inputType: "text", responseText: ["카드 응답입니다."], responseButtons: [ResponseButton(name: "블록", type: "blockLink", nextBlock: NextBlock(id: "blockId", name: "blockName", blockIndex: "blockIndex"), webLinkUrl: "", appLinkUrl: ""), ResponseButton(name: "웹", type: "webLink", nextBlock: NextBlock(id: nil, name: nil, blockIndex: nil), webLinkUrl: "https://webLinkUrl", appLinkUrl: ""), ResponseButton(name: "앱", type: "appLink", nextBlock: nil, webLinkUrl: "", appLinkUrl: "https://appLinkUrl")], responseTitle: "카드 응답의 헤더입니다.", imageUrl: "https://imageUrl"))
    }
}

# 별첨2. JSONSerialization Code

import UIKit

class SimpleJson {
    init() {}
    var name: String = ""
    var message: String = ""
}

if let url = URL(string: "http://localhost:8080/simple") {
    var request = URLRequest.init(url: url)

    request.httpMethod = "GET"

    URLSession.shared.dataTask(with: request) { (data, response, error) in
        guard let data = data else { return }

        // data
        if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] {
            let simpleJson = SimpleJson()
            if let name = json["name"] as? String {
                simpleJson.name = name
                print(name)
            }
        }
    }.resume()
}

class ComplexJson: Codable {
    init() {}
    var code: String = ""
    var data: BotData = BotData()
}

class BotData: Codable {
    init() {}
    var contentType: [String] = []
    var inputType: String = ""
    var responseText: [String] = []
    var responseButtons: [ResponseButton]?
    var responseTitle: String?
    var imageUrl: String?
}

class ResponseButton: Codable {
    init() {}
    var name: String = ""
    var type: String = ""
    var nextBlock: NextBlock?
    var webLinkUrl: String = ""
    var appLinkUrl: String = ""
}

class NextBlock: Codable {
    init() {}
    var id: String?
    var name: String?
    var blockIndex: String?
}

if let url = URL(string: "http://localhost:8080/complex") {
    var request = URLRequest.init(url: url)

    request.httpMethod = "GET"

    URLSession.shared.dataTask(with: request) { (data, response, error) in
        guard let data = data else { return }

        // data
        if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] {
            let complexJson = ComplexJson()
            if let code = json["code"] as? String {
                complexJson.code = code
                print(code) // 1000
            }
            if let data = json["data"] as? [String : Any], let contentType = data["contentType"] as? [String]{
                complexJson.data.contentType = contentType
                print(contentType)
                if contentType.contains("textRandom") {
                    if let responseText = data["responseText"] as? [String] {
                        complexJson.data.responseText = responseText
                        print(responseText)
                    }
                } else if contentType.contains("image") {
                    if let imageUrl = data["imageUrl"] as? String {
                        complexJson.data.imageUrl = imageUrl
                        print(imageUrl)
                    }
                } else if contentType.contains("card") {
                    if let responseTitle = data["responseTitle"] as? String, let responseText = data["responseText"] as? [String], let imageUrl = data["imageUrl"] as? String {
                        complexJson.data.responseTitle = responseTitle
                        complexJson.data.responseText = responseText
                        complexJson.data.imageUrl = imageUrl
                        print(responseTitle)
                        print(responseText)
                        print(imageUrl)
                    }
                }

                if contentType.contains("button") {
                    var responseButtonList: [ResponseButton] = []

                    if let responseButtons = data["responseButtons"] as? [[String : Any]] {
                        print(responseButtons)

                        for i in 0..<responseButtons.count {
                            let responseButton = ResponseButton()
                            if let name = responseButtons[i]["name"] as? String, let type = responseButtons[i]["type"] as? String, let nextBlock = responseButtons[i]["nextBlock"] as? [String : String], let webLinkUrl = responseButtons[i]["webLinkUrl"] as? String, let appLinkUrl = responseButtons[i]["appLinkUrl"] as? String {
                                responseButton.name = name
                                responseButton.type = type
                                responseButton.webLinkUrl = webLinkUrl
                                responseButton.appLinkUrl = appLinkUrl

                                let nextBlock2 = NextBlock()
                                if let nextBlockId = nextBlock["id"], let nextBlockName = nextBlock["name"], let nextBlockBlockIndex = nextBlock["blockIndex"] {
                                    nextBlock2.id = nextBlockId
                                    nextBlock2.name = nextBlockName
                                    nextBlock2.blockIndex = nextBlockBlockIndex
                                }

                                responseButton.nextBlock = nextBlock2
                            }
                            responseButtonList.append(responseButton)
                        }
                    }
                    complexJson.data.responseButtons = responseButtonList
                }
            }
        }
    }.resume()
}

# 별첨3. Codable Code

import UIKit

struct SimpleJson: Codable {
    var name: String
    var message: String
}

if let url = URL(string: "http://localhost:8080/simple") {
    var request = URLRequest.init(url: url)

    request.httpMethod = "GET"

    URLSession.shared.dataTask(with: request) { (data, response, error) in
        guard let data = data else { return }

        // data
        let decoder = JSONDecoder()
        if let json = try? decoder.decode(SimpleJson.self, from: data) {
            print(json.name) // hyeon
        }
    }.resume()
}

struct ComplexJson: Codable {
    let code: String
    let data: BotData
}

struct BotData: Codable {
    let contentType: [String]
    let inputType: String
    let responseText: [String]
    let responseButtons: [ResponseButton]?
    let responseTitle: String?
    let imageUrl: String?
}

struct ResponseButton: Codable {
    let name: String
    let type: String
    let nextBlock: NextBlock?
    let webLinkUrl: String
    let appLinkUrl: String
}

struct NextBlock: Codable {
    let id: String?
    let name: String?
    let blockIndex: String?
}

if let url = URL(string: "http://localhost:8080/complex") {
    var request = URLRequest.init(url: url)

    request.httpMethod = "GET"

    URLSession.shared.dataTask(with: request) { (data, response, error) in
        guard let data = data else { return }

        // data
        let decoder = JSONDecoder()
        if let json = try? decoder.decode(ComplexJson.self, from: data) {
            print(json.code)

            if json.data.contentType.contains("textRandom") {
                print(json.data.responseText)
            } else if json.data.contentType.contains("image") {
                if let imageUrl = json.data.imageUrl {
                    print(imageUrl)
                }
            } else if json.data.contentType.contains("card") {
                if let responseTitle = json.data.responseTitle, let imageUrl = json.data.imageUrl {
                    print(responseTitle)
                    print(json.data.responseText)
                    print(imageUrl)
                }
            }

            if json.data.contentType.contains("button") {
                if let responseButtons = json.data.responseButtons {
                    print(responseButtons)
                }
            }

        }
    }.resume()
}

'Swift' 카테고리의 다른 글

[Swift] Framework 만들기  (1) 2020.07.10
[Swift] Delay 이후 실행  (0) 2020.06.25
[Swift] UITextField, UITextView에서 엔터 감지하기  (0) 2020.06.04
[Swift] API 통신 URLSession / Alamofire  (0) 2020.05.28
[Swift] http basic auth  (0) 2020.05.11
댓글