0Pricing
Swift Academy · Lekcja

Ręczne encode(to:) i init(from:)

Przejmowanie pełnej kontroli nad logiką serializacji.

Ręczne encode(to:) i init(from:) to bezpłatna lekcja Swift Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Swift Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Swift Academy zawiera 4 lekcji w sumie.

Gdy synteza nie wystarcza

Czasami struktura JSON nie odpowiada właściwościom — potrzebne są obliczane przekształcenia, wartości domyślne lub spłaszczanie struktury. W takiej sytuacji metody encode(to:) i init(from:) należy zaimplementować ręcznie.

import Foundation

struct Temperature: Codable {
    var celsius: Double
}

print("Manual coding gives full control")

Najpierw należy zadeklarować CodingKeys

Ręczne kodowanie zaczyna się od enumeracji CodingKeys. Kontenery kluczowane używają tych kluczy do odczytywania i zapisywania wartości.

import Foundation

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

print(User.CodingKeys.name.stringValue)

Implementacja init(from:)

init(from decoder:) pobiera kontener kluczowany za pomocą container(keyedBy:), a następnie dekoduje każdą właściwość na podstawie jej klucza.

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)

Implementacja encode(to:)

encode(to encoder:) pobiera modyfikowalny kontener kluczowany za pomocą container(keyedBy:) i zapisuje każdą właściwość przy użyciu 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)!)

Pełna implementacja Codable ręcznie

Implementacja obu metod zapewnia typowi pełną zgodność z Codable oraz niestandardową logikę po obu stronach.

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)

Wprowadzanie wartości domyślnych

Należy użyć decodeIfPresent wraz z operatorem nil-coalescing, aby podać wartość domyślną, gdy brakuje klucza, zamiast zgłaszać błąd.

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)

Przekształcanie wartości

Ręczne dekodowanie pozwala przekształcić surowy JSON w bogatszą reprezentację — w tym przypadku wartość wejściowa w stopniach Fahrenheita jest przechowywana w stopniach Celsjusza.

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)

Spłaszczanie zagnieżdżonego JSON

Za pomocą nestedContainer(keyedBy:forKey:) można odczytać zagnieżdżony kontener i przenieść jego wartości do płaskiej struktury 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)

Kodowanie do zagnieżdżonego kontenera

Kodowanie może działać odwrotnie: zapisać płaską strukturę jako zagnieżdżony obiekt JSON za pomocą 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)!)

Walidacja podczas dekodowania

Ponieważ init(from:) jest zwykłym inicjalizatorem, można w nim sprawdzać wartości i zgłaszać własny błąd, gdy wykraczają poza dozwolony zakres.

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)

Łączenie implementacji ręcznej i syntezy

Typ może implementować ręcznie tylko jedną metodę, a drugą pozostawić syntezie kompilatora — na przykład używać własnego init(from:) i automatycznie syntetyzowanego kodowania.

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)!)

Szybki test: kodowanie ręczne

Proszę sprawdzić zrozumienie ręcznie napisanych metod Codable.

Podsumowanie: ręczne encode(to:) i init(from:)

Poznali Państwo pełną kontrolę nad kodowaniem:

  • Należy zadeklarować CodingKeys, a następnie zaimplementować init(from:) i encode(to:).
  • Należy używać container(keyedBy:) oraz encode/decode(_:forKey:).
  • decodeIfPresent wraz z ?? zapewnia wartości domyślne, a nestedContainer spłaszcza lub zagnieżdża JSON.
  • Można przeprowadzać walidację, przekształcać wartości oraz łączyć metody ręczne z syntetyzowanymi.

Często zadawane pytania

Czy lekcja „Ręczne encode(to:) i init(from:)” jest bezpłatna?

Tak — pełny tekst „Ręczne encode(to:) i init(from:)” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Swift Academy, przejdź na CoddyKit PRO. Kurs Swift Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Ręczne encode(to:) i init(from:)”?

Przejmowanie pełnej kontroli nad logiką serializacji. Ćwiczysz Swift Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Swift Academy?

Nie wymagamy żadnego doświadczenia. Swift Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Ręczne encode(to:) i init(from:)”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Swift Academy?

Tak. Każda lekcja Swift Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. CodingKeys do zmiany nazw
  2. Strategie dekodowania kluczy i dat
  3. Ręczne encode(to:) i init(from:)
  4. Dekodowanie heterogenicznego JSON
← Powrót do Swift Academy