Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Written by chuotfx on March 24, 2020

Encoding và Decoding trong Swift

iOS & Swift

Contents

  • Chuẩn bị
  • 1. Overview
    • 1.1. Encodable
    • 1.2. Decodable
    • 1.3. Codable
    • 1.4. Ví dụ
  • 2. Automatic Encoding and Decoding
  • 3. Encoding and Decoding Custom Types
    • 3.1. JSONEncoder and JSONDecoder
      • 3.1.1. JSONEncoder
      • 3.1.2. JSONDecoder
    • 3.2. Working With Custom JSON Keys
  • 4. Manual Encoding and Decoding
    • 4.1. Subsclass
    • 4.2. Encode function
    • 4.3. Decode function
  • Tạm kết

Chào bạn đến với Fx Studio,

Bài viết hôm nay sẽ nói về chủ đề khá quen thuộc trong lập trình iOS. Đó là Encoding và Decoding trong Swift.

Chuẩn bị

Bạn chỉ cần làm việc với Playground là đủ rồi, không cần phải chuẩn bị gì nhiều thêm. Với Swift thì 4.0 trở lên.

1. Overview

Đây là một vấn đề 2 chiều, bạn hãy tưởng tượng ra một ví dụ như thế này:

  • Lưu một đối tượng thành dữ liệu vào file/DB/gởi tới server
  • Biến đổi dữ liệu nhận được thành đối tượng

Công việc này bạn lặp đi lặp lại hằng ngày, biến đổi và lưu trữ dữ liệu thành các đối tượng với kiểu dữ liệu phù hợp. Nhất là khi bạn làm việc với Networking, tương tác với API để lấy dữ liệu về. Nghe qua thì công việc có gì vất vả đâu nhĩ?

Nhưng với 1 đối tượng có tới cả chục thuộc tính, mà cấu trúc JSON bạn nhận được lại lồng rất nhiều vòng. Càng đau khổ hơn nữa là dữ liệu nhận được có thể đúng có thể sai. Trường hợp kinh điển như ví dụ sau:

Ví dụ:

  • Bạn nhận được "9999"
  • Đó là kiểu String, nhưng người làm backend lại đang gởi cho bạn kiểu Int.

Vâng vâng và mây mây, cái trên chỉ nói tới một vấn đề đơn giản là chuyển đổi qua lại giữa các kiểu dữ liệu một cách nhanh chóng và an toàn nhất. Ta có:

  • Quá trình chuyển đổi đó gọi là encoding hay còn được biết như là serialization.
  • Quá trình chuyển đổi ngược lại từ dữ liệu đã mã hoá về dữ liệu ban đầu được gọi là decoding hay còn gọi là deserialization.

Chúng ta sẽ có 3 protocol sau:

1.1. Encodable

Giao thức Encodable được sử dụng bởi các loại có thể được mã hóa sang biểu diễn khác. Nó khai báo như sau:

func encode(to: Encoder) throws

1.2. Decodable

Giao thức Decodable được sử dụng bởi các loại có thể được giải mã. Được khai báo như sau:

init(from decoder: Decoder) throws

1.3. Codable

Codable là một phương thức chung khai báo cho việc encode và decode. Khai báo như sau:

typealias Codable = Encodable & Decodable

1.4. Ví dụ

Phải mục sở thị một lần mới biết được. Ta có 1 json object như sau:

let json: [ String : Any ] =
  [
    "name" : "Chuột FX",
    "age"  : 18,
    "dog"  : [
      "name" : "Pochi",
      "age"  : 2
    ]
]

Để phục vụ cho Encoding và Decoding thì chúng ta mượn tạm 2 đối tượng sau:

let encoder = JSONEncoder()
let decoder = JSONDecoder()
  • Bước 1 : biến json object  thành data
let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
  • Bước 2 : biến data thành String
let string = String(data: data, encoding: .utf8)!

Dữ liệu cuối cùng biến đổi được như sau:

{
  "dog" : {
    "age" : 2,
    "name" : "Pochi"
  },
  "age" : 18,
  "name" : "Chuột FX"
}

Và bạn có thể làm ngược lại. Mô tả 1 json object bằng 1 String, rồi với Encoding và Decoding, để biến đổi nó thành Dictionary.

2. Automatic Encoding and Decoding

Ta có các kiểu dữ liệu cơ bản từ Standard Library và Foundation framework như Int, Float, String … thì có thể được Encoding và Decoding một cách tự động. Và bạn muốn kiểu dữ liệu của mình tạo ra cũng tự động có được siêu năng lực như vậy thì:

Tất các các thuộc tính đều phải là các kiểu dữ liệu có thể Encoding và Decoding được.

Khà khà, lấy ví dụ cho dễ hình dung nào. Ta có 1 struct User như sau:

struct User {
  var name : String
  var age : Int
}

Tất cả những gì bạn cần làm để có thể Encoding và Decoding loại này là tuân thủ giao thức Codable, như sau:

struct User : Codable {
  var name : String
  var age : Int
}

EZ Game! Bạn đã có thể làm điều đó, vì cả name (String) và age (Int) đều có thể mã hóa được. Điều này hoạt động tốt khi bạn chỉ sử dụng các loại đã Codable. Nhưng nếu loại của bạn bao gồm các loại tùy chỉnh khác cho thuộc tính thì sao?

Lại xem tiếp ví dụ, bây giờ mình có thêm 1 thuộc tính dog với kiểu dữ liệu là Dog:

struct User : Codable {
  var name : String
  var age : Int
  var dog : Dog
}

Thì struct Dog kia cũng phải tuân thủ đúng theo giao thức Codable. Và ta có khai báo như sau:

struct Dog : Codable {
  var name : String
  var age : Int
}

Bây giờ thử xem biến đổi như thế nào nha:

  • Tạo 2 đối tượng từ 2 struct User & Dog
let dog = Dog(name: "Milu", age: 2)
let user = User(name: "Fx", age: 19, dog: dog)
  • Vẫn dùng 2 đối tượng phục vụ Encoding và Decoding
let encoder = JSONEncoder()
let decoder = JSONDecoder()
  • Chuyển đổi đối tượng User thành data
let data = try encoder.encode(user)
  • Biến data thành String
let string = String(data: data, encoding: .utf8)!

Và đây là kết quả nhận được:

{"name":"Fx","age":19,"dog":{"name":"Milu","age":2}}

 

3. Encoding and Decoding Custom Types

Có rất nhiều kiểu dữ liệu mà bạn có thể biến đổi được, như: XML, Properties List, JSON … Và phần này thì ta sẽ tập trung vào việc biến đổi qua lại giữa

JSON và Custom Type

Tại sao là JSON? Đơn giản vì nó hiện tại đang rất là phổ biến. Được xem là chuẩn dữ liệu cho việc tương tác Networking.

3.1. JSONEncoder and JSONDecoder

JSON là viết tắt của JavaScript Object Notation và là một trong những cách phổ biến nhất để tuần tự hóa dữ liệu. Nó có thể dễ dàng đọc được bởi con người và dễ dàng cho máy tính phân tích và tạo ra.

Hai class mà chúng ta dùng để Encoding và Decoding như sau:

  • JSONEncode
  • JSONDecode

Bạn có thể mô tả đối tượng User ở trên bằng JSON như sau:

{
   "name": "Fx",
   "age": 19,
   "dog": {
     "name":"Milu",
     "age":2
    }
}

Bạn có thể dễ dàng hiểu được cá thể User trông như thế nào trước khi nó được tuần tự hóa thành JSON.

3.1.1. JSONEncoder

Khi bạn có một loại Codable, bạn có thể sử dụng JSONEncoder để chuyển đổi loại của bạn thành Data có thể được ghi vào một tệp hoặc được gửi qua mạng. Giả sử bạn có ví dụ User này:

let dog = Dog(name: "Milu", age: 2)
let user = User(name: "Fx", age: 19, dog: dog)

Tạo đối tượng encode

let encoder = JSONEncoder()

Biến nó thành data để tiện tay gởi đi đâu thì gởi

let data = try encoder.encode(user)

3.1.2. JSONDecoder

Muốn biến đổi ngược lại thì cần phải đối tượng giải mã và ta sử dụng class JSONDecoder

let decoder = JSONDecoder()

Việc còn lại thì khá là EZ

let anotherUser = try decoder.decode(User.self, from: data)

print(anotherUser)

Kết quả in ra như sau:

User(name: "Fx", age: 19, dog: __lldb_expr_1.Dog(name: "Milu", age: 2))

 

3.2. Working With Custom JSON Keys

Nếu như một ngày đẹp trời, đồng đội của bạn trong dự án, là 1 backend. Quyết định tăng độ khó cho game, âm thầm đổi key của cấu trúc JSON. Bạn lại phải dò lại từ đầu và thay đổi từng thuộc tính cho phù hợp. Và ông trời không tuyệt đường sống của ai bao giờ. Bạn xem tiếp phần sau.

let json: [ String : Any ] =
  [
    "fullname" : "Chuột FX",
    "age"  : 18,
    "dog"  : [
      "name" : "Pochi",
      "age"  : 2
    ]
]

Bạn chú ý fullname là key mới, nó khác với property name của User đã được khai báo ở trên. Công việc lúc này là chỉnh sửa lại một tí:

struct User : Codable {
  var name : String
  var age : Int
  var dog : Dog
  
  enum CodingKeys: String, CodingKey {
    case name = "fullname"
    case age
    case dog
  }
}

Enum CodingKeys phù hợp với giao thức CodingKey, cho phép bạn đổi tên các thuộc tính cụ thể trong trường hợp định dạng được tuần tự hóa không phù hợp với các yêu cầu của API.

Lưu ý như sau:

  • CodingKeys là một phép liệt kê lồng nhau trong kiểu của bạn.
  • Nó phải là 1 loại CodingKey
  • Các khoá phải là kiểu String
  • Bạn phải bao gồm tất cả các thuộc tính trong bảng liệt kê, ngay cả khi bạn không có kế hoạch đổi tên chúng.
  • Theo mặc định, phép liệt kê này được tạo bởi trình biên dịch, nhưng khi bạn cần đổi tên một khóa, bạn cần phải tự thực hiện nó.

Xem chúng hoạt động ổn không nào.

  • Biến đổi json object thành data
let encoder = JSONEncoder()
let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
  • Biến đổi data thành User
let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: data)

print(user)

Kết quả in ra như sau:

User(name: "Chuột FX", age: 18, dog: __lldb_expr_1.Dog(name: "Pochi", age: 2))

 

4. Manual Encoding and Decoding

4.1. Subsclass

Khi bạn nhận được một sự thay đổi nữa từ người bạn thân thiện backend kia trong dự án của bạn. Bạn ta lần này không chỉ đổi tên thuộc tính mà còn thay đổi cả cấu trúc JSON.

{
  "fullname" : "Chuột FX"
  "age" : 18,
  "pet" : "Milu"
}

Và lần này độ khó cho game đã tăng lên rất nhiều. Bạn không thể xoá đi viết lại cả hàng trăm file source code. Nhưng trời xanh vẫn cho ta một con đường sống.

Bạn tiến hành sử dụng phương pháp cuối này. Tạo sub-class để tối ưu cho việc Encoding và Decoding. Cũng như có thể tương thích với các kiểu dữ liệu khác không phải Codable. Okay, chiến thôi!

Vẫn ví dụ trên, ta tạo 1 class tên là BaseUser như sau:

class BaseUser : Codable {
  var name : String
  var age : Int
  var dog : Dog
  
  enum CodingKeys: String, CodingKey {
    case name = "fullname"
    case age
    case dog = "pet"
  }
}

Mọi việc vẫn chưa có gì thay đổi quá nhiều. Bạn cần kế thừa protocol Codable để có thể Encoding và Decoding. Ngoài ra, điều bắt buộc với các sub-class thì cần phải khai báo enum CodingKeys. Bạn chú ý phải tương thích với cấu trúc JSON.

Tiếp theo là cần tạo hoàn cảnh cho class này thực thi. Bạn có một json object như sau:

let json: [ String : Any ] =
  [
    "fullname" : "Chuột FX",
    "age"  : 18,
    "pet"  : "Milu"
]

Vẫn là công việc cũ, biến đổi nó thành data. Cái này giả lập việc bạn nhận data trực tiếp từ server hay đọc file.

let encoder = JSONEncoder()
let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)

OKAY! Sang 2 phần tiếp theo nào.

4.2. Encode function

Codable thực sự chỉ là một kiểu dành cho các giao thức Encodable và Decodable. Bạn cần triển khai encode(to: Encoder) và mô tả cách mã hóa từng thuộc tính.

Chúng ta thêm function encode vào cho BaseUser

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    
    try container.encode(name, forKey: .name)
    try container.encode(age, forKey: .age)
    try container.encode(dog.name, forKey: .dog)
  }

Đầu tiên bạn lấy lại container của bộ encoder. Đây là chế độ xem vào bộ lưu trữ của bộ encoder mà bạn có thể truy cập bằng các key.

Lưu ý cách bạn chọn thuộc tính nào để mã hóa cho key nào. Bạn sẽ thấy với key dog, chúng ta lại dùng dog.name của đối tượng.

4.3. Decode function

Cuối cùng là bạn biến đổi dữ liệu nhận được thành chính đối tượng của bạn. Tuy nhiên, lần này cần phải thao tác thêm. Và bạn sử dụng function decode của protocol Decodable.

Bạn thêm function này vào BaseUser

required init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    
    name = try values.decode(String.self, forKey: .name)
    age = try values.decode(Int.self, forKey: .age)
    
    let pet = try values.decode(String.self, forKey: .dog)
    
    dog = Dog(name: pet, age: 0)
  }

Chúng ta sử dụng init , vì đây là phương pháp tạo ra một đối tượng. Bạn cũng sẽ thấy việc biến đổi ngược lại:

  • Lấy values từ container của decoder
  • Trích xuất từng dữ liệu phù hợp với từng thuộc tính của đối tượng, chúng tương ứng với nhau qua CodingKeys
  • Với thuộc tính nào cần phải biến đổi, thì thêm một chút gia vị cho nó đậm đà. Với dog, chúng ta cần phải tạo đối tượng mới từ struct Dog với dữ liệu pet cho name của Dog.

Xem hoạt động như thế nào, tiếp tục với đoạn code ở trên. Chúng ta sử dụng data vừa được mã hoá để phục vụ cho việc giải mã.

let decoder = JSONDecoder()
let baseUser = try decoder.decode(BaseUser.self, from: data)

print(baseUser.dog)

Kết quả in ra như sau:

Dog(name: "Milu", age: 0)

 

OKAY! Chúng ta sẽ kết thúc bài viết này tại đây. Hi vọng chúng sẽ giúp ích được cho bạn trong việc chiến đấu với các anh bạn backend thân thiện. Và bạn có thể sử dụng bài viết này để bổ sung kiến thức cho series Lập trình iOS cho mọi người

Tạm kết

  • Tìm hiểu về các protocol Encodable, Decodable và Codable
  • Cách hoạt động của Encoding và Decoding
  • Các cách chuyển đổi dữ liệu qua lại tự động
  • Custom kiểu dữ liệu để có thể Codable
  • Tuỳ biến các key với CodingKeys
  • Xử lý bằng tay với các cấu trúc JSON phức tạp hoặc có điều chỉnh riêng với các function encode và decode

(Bài viết được tham khảo từ raywenderlich.com.)

Cảm ơn bạn đã đọc bài viết này!

FacebookTweetPinYummlyLinkedInPrintEmailShares22

Related Posts:

  • Dart Defines
    Dart Defines trong Flutter và sức mạnh của nó
  • KeyPath
    KeyPath trong 10 phút - Swift
  • Flavor
    Flavor & Câu chuyện config trong Flutter
  • feature_bg_swiftui_4
    Regular Expression (Regex) trong Swift
Tags: decoding, encoding, Swift
Written by chuotfx

Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!

2 comments

  • Tài has written: October 25, 2021 at 10:20 pm Reply

    Cảm ơn Bác đã viết ra những bài hay như này. Để ae lấn sân sang iOS hoặc newbie có cái nhìn trực quan hơn. <3

    • chuotfx has written: October 25, 2021 at 10:31 pm Reply

      Thank you! <3

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift AI api AppDistribution autolayout basic ios tutorial blog ci/cd closure collectionview combine concurrency crashlytics dart dart basic dart tour Declarative delegate deploy design pattern fabric fastlane firebase flavor flutter GCD gradients iOS MVVM optional Prompt engineering protocol Python rxswift safearea Swift Swift 5.5 SwiftData SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Role-playing vs. Persona-based Prompting
  • [Swift 6.2] Raw Identifiers – Đặt tên hàm có dấu cách, tại sao không?
  • Vibe Coding là gì?
  • Cách Đọc Sách Lập Trình Nhanh và Hiệu Quả Bằng GEN AI
  • Nỗ Lực – Hành Trình Kiến Tạo Ý Nghĩa Cuộc Sống
  • Ai Sẽ Là Người Fix Bug Khi AI Thống Trị Lập Trình?
  • Thời Đại Của “Dev Tay To” Đã Qua Chưa?
  • Prompt Engineering – Con Đường Để Trở Thành Một Nghề Nghiệp
  • Vấn đề Ảo Giác (hallucination) khi tương tác với Gen AI và cách khắc phục nó qua Prompt
  • Điều Gì Xảy Ra Nếu… Những Người Dệt Mã Trở Thành Những Người Bảo Vệ Cuối Cùng Của Sự Sáng Tạo?

You may also like:

  • Flavor & Câu chuyện config trong Flutter
    Flavor
  • Observation Framework trong 10 phút
    feature_bg_swiftui_4
  • Regular Expression (Regex) trong Swift
    feature_bg_swiftui_4
  • Raw String trong 10 phút
    Raw String
  • Dart Defines trong Flutter và sức mạnh của nó
    Dart Defines

Archives

  • May 2025 (2)
  • April 2025 (1)
  • March 2025 (8)
  • January 2025 (7)
  • December 2024 (4)
  • September 2024 (1)
  • July 2024 (1)
  • June 2024 (1)
  • May 2024 (4)
  • April 2024 (2)
  • March 2024 (5)
  • January 2024 (4)
  • February 2023 (1)
  • January 2023 (2)
  • November 2022 (2)
  • October 2022 (1)
  • September 2022 (5)
  • August 2022 (6)
  • July 2022 (7)
  • June 2022 (8)
  • May 2022 (5)
  • April 2022 (1)
  • March 2022 (3)
  • February 2022 (5)
  • January 2022 (4)
  • December 2021 (6)
  • November 2021 (8)
  • October 2021 (8)
  • September 2021 (8)
  • August 2021 (8)
  • July 2021 (9)
  • June 2021 (8)
  • May 2021 (7)
  • April 2021 (11)
  • March 2021 (12)
  • February 2021 (3)
  • January 2021 (3)
  • December 2020 (3)
  • November 2020 (9)
  • October 2020 (7)
  • September 2020 (17)
  • August 2020 (1)
  • July 2020 (3)
  • June 2020 (1)
  • May 2020 (2)
  • April 2020 (3)
  • March 2020 (20)
  • February 2020 (5)
  • January 2020 (2)
  • December 2019 (12)
  • November 2019 (12)
  • October 2019 (19)
  • September 2019 (17)
  • August 2019 (10)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (44)
  • Code (11)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (102)
  • No Category (1)
  • RxSwift (37)
  • SwiftUI (80)
  • Tutorials (87)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2025 Fx Studio - All rights reserved.