0Pricing
Swift Academy · Урок

Ручные encode(to:) и init(from:)

Полностью управляйте логикой сериализации.

«Ручные encode(to:) и init(from:)» — бесплатный урок Swift Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Swift Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Swift Academy содержит 4 уроков всего.

Когда синтеза недостаточно

Иногда структура JSON не соответствует Вашим свойствам: требуются вычисляемые преобразования, значения по умолчанию или упрощение вложенной структуры. В таком случае реализуйте encode(to:) и init(from:) вручную.

import Foundation

struct Temperature: Codable {
    var celsius: Double
}

print("Manual coding gives full control")

Сначала объявите CodingKeys

Ручное кодирование начинается с перечисления CodingKeys. Контейнеры с ключами используют эти ключи для чтения и записи значений.

import Foundation

struct User {
    var name: String
    var age: Int
    enum CodingKeys: String, CodingKey {
        case name
        case age
    }
}

print(User.CodingKeys.name.stringValue)

Реализация init(from:)

init(from decoder:) получает контейнер с ключами с помощью container(keyedBy:), а затем декодирует каждое свойство по его ключу.

import Foundation

struct User: Decodable {
    var name: String
    var age: Int
    enum CodingKeys: String, CodingKey { case name, age }
    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        name = try c.decode(String.self, forKey: .name)
        age = try c.decode(Int.self, forKey: .age)
    }
}

let u = try JSONDecoder().decode(User.self, from: "{\"name\":\"Ada\",\"age\":36}".data(using: .utf8)!)
print(u.name, u.age)

Реализация encode(to:)

encode(to encoder:) получает изменяемый контейнер с ключами с помощью container(keyedBy:) и записывает каждое свойство через encode(_:forKey:).

import Foundation

struct User: Encodable {
    var name: String
    var age: Int
    enum CodingKeys: String, CodingKey { case name, age }
    func encode(to encoder: Encoder) throws {
        var c = encoder.container(keyedBy: CodingKeys.self)
        try c.encode(name, forKey: .name)
        try c.encode(age, forKey: .age)
    }
}

let data = try JSONEncoder().encode(User(name: "Ada", age: 36))
print(String(data: data, encoding: .utf8)!)

Полный Codable вручную

Реализация обоих методов делает тип полностью Codable и позволяет использовать пользовательскую логику на обоих этапах.

import Foundation

struct Point: Codable {
    var x: Int
    var y: Int
    enum CodingKeys: String, CodingKey { case x, y }
    init(x: Int, y: Int) { self.x = x; self.y = y }
    init(from d: Decoder) throws {
        let c = try d.container(keyedBy: CodingKeys.self)
        x = try c.decode(Int.self, forKey: .x)
        y = try c.decode(Int.self, forKey: .y)
    }
    func encode(to e: Encoder) throws {
        var c = e.container(keyedBy: CodingKeys.self)
        try c.encode(x, forKey: .x)
        try c.encode(y, forKey: .y)
    }
}

let back = try JSONDecoder().decode(Point.self, from: try JSONEncoder().encode(Point(x: 1, y: 2)))
print(back.x, back.y)

Задание значений по умолчанию

Используйте decodeIfPresent вместе с оператором объединения с nil, чтобы подставить значение по умолчанию при отсутствии ключа, вместо того чтобы выбрасывать ошибку.

import Foundation

struct Settings: Decodable {
    var theme: String
    enum CodingKeys: String, CodingKey { case theme }
    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        theme = try c.decodeIfPresent(String.self, forKey: .theme) ?? "light"
    }
}

let s = try JSONDecoder().decode(Settings.self, from: "{}".data(using: .utf8)!)
print(s.theme)

Преобразование значений

Ручное декодирование позволяет преобразовать исходный JSON в более содержательное представление — в данном случае значение температуры, переданное в градусах Фаренгейта, сохраняется в градусах Цельсия.

import Foundation

struct Reading: Decodable {
    var celsius: Double
    enum CodingKeys: String, CodingKey { case fahrenheit }
    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        let f = try c.decode(Double.self, forKey: .fahrenheit)
        celsius = (f - 32) * 5 / 9
    }
}

let r = try JSONDecoder().decode(Reading.self, from: "{\"fahrenheit\":212}".data(using: .utf8)!)
print(r.celsius)

Упрощение вложенного JSON

Вы можете прочитать вложенный контейнер с помощью nestedContainer(keyedBy:forKey:) и поднять его значения на уровень плоской структуры Swift.

import Foundation

struct Profile: Decodable {
    var city: String
    enum Keys: String, CodingKey { case address }
    enum AddressKeys: String, CodingKey { case city }
    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: Keys.self)
        let a = try c.nestedContainer(keyedBy: AddressKeys.self, forKey: .address)
        city = try a.decode(String.self, forKey: .city)
    }
}

let json = "{\"address\":{\"city\":\"Rome\"}}"
let p = try JSONDecoder().decode(Profile.self, from: json.data(using: .utf8)!)
print(p.city)

Кодирование во вложенный контейнер

Кодирование может выполнить обратное действие: записать плоскую структуру в виде вложенного объекта JSON с помощью nestedContainer(keyedBy:forKey:).

import Foundation

struct Profile: Encodable {
    var city: String
    enum Keys: String, CodingKey { case address }
    enum AddressKeys: String, CodingKey { case city }
    func encode(to encoder: Encoder) throws {
        var c = encoder.container(keyedBy: Keys.self)
        var a = c.nestedContainer(keyedBy: AddressKeys.self, forKey: .address)
        try a.encode(city, forKey: .city)
    }
}

let data = try JSONEncoder().encode(Profile(city: "Rome"))
print(String(data: data, encoding: .utf8)!)

Проверка во время декодирования

Поскольку init(from:) — всего лишь инициализатор, Вы можете проверить значения и выбросить пользовательскую ошибку, если они выходят за допустимый диапазон.

import Foundation

struct Score: Decodable {
    var value: Int
    enum CodingKeys: String, CodingKey { case value }
    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        let v = try c.decode(Int.self, forKey: .value)
        guard (0...100).contains(v) else {
            throw DecodingError.dataCorruptedError(forKey: .value, in: c, debugDescription: "out of range")
        }
        value = v
    }
}

let s = try JSONDecoder().decode(Score.self, from: "{\"value\":80}".data(using: .utf8)!)
print(s.value)

Сочетание ручного и синтезированного кода

Тип может вручную реализовать только один метод, а второй предоставить компилятору для синтеза — например, использовать пользовательский init(from:) и синтезированное кодирование.

import Foundation

struct Flag: Codable {
    var on: Bool
    enum CodingKeys: String, CodingKey { case on }
    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        on = (try? c.decode(Bool.self, forKey: .on)) ?? false
    }
    init(on: Bool) { self.on = on }
}

let data = try JSONEncoder().encode(Flag(on: true))
print(String(data: data, encoding: .utf8)!)

Проверка: ручное кодирование

Проверьте, насколько хорошо Вы поняли написанные вручную методы Codable.

Итоги: ручные encode(to:) и init(from:)

Вы научились полностью управлять кодированием:

  • Объявляйте CodingKeys, затем реализуйте init(from:) и encode(to:).
  • Используйте container(keyedBy:) и encode/decode(_:forKey:).
  • decodeIfPresent вместе с ?? подставляет значения по умолчанию; nestedContainer упрощает или вкладывает JSON.
  • Вы можете проверять и преобразовывать значения, а также сочетать ручные и синтезированные методы.

Часто задаваемые вопросы

Урок «Ручные encode(to:) и init(from:)» бесплатный?

Да — полный текст урока «Ручные encode(to:) и init(from:)» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Swift Academy, подпишись на CoddyKit PRO. Курс Swift Academy содержит 4 уроков всего.

Чему я научусь в уроке «Ручные encode(to:) и init(from:)»?

Полностью управляйте логикой сериализации. Ты практикуешь Swift Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Swift Academy?

Предыдущий опыт не требуется. Swift Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Ручные encode(to:) и init(from:)»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Swift Academy?

Да. Каждый урок Swift Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. CodingKeys для переименования
  2. Стратегии декодирования ключей и дат
  3. Ручные encode(to:) и init(from:)
  4. Декодирование неоднородного JSON
← Назад к Swift Academy