Codable cheat sheet

Codablebył jednym z kamieni węgielnych funkcji Swift 4.0, przynosząc ze sobą niewiarygodnie płynną konwersję między typami danych Swift i JSON. Następnie stała się jeszcze lepsza w Swift 4.1 dzięki dodaniu nowej funkcjonalności i spodziewam się jeszcze większych rzeczy w przyszłości.

W tym artykule chcę dostarczyć szybkie próbki kodu, aby pomóc Ci odpowiedzieć na powszechne pytania i rozwiązać powszechne problemy, wszystkie przy użyciu Codable.

Hacking with Swift jest sponsorowany przez RevenueCat

SPONSOROWANE Budowanie i utrzymywanie infrastruktury subskrypcji in-app jest trudne. Na szczęście jest na to lepszy sposób. Dzięki RevenueCat możesz wdrożyć subskrypcje dla swojej aplikacji w ciągu godzin, a nie miesięcy, dzięki czemu możesz wrócić do budowania aplikacji.

Wypróbuj za darmo

Sponsoruj Hacking with Swift i dotrzyj do największej na świecie społeczności Swift!

Kodowanie i dekodowanie JSON

Zacznijmy od podstaw: konwertowanie niektórych JSON na struktury Swift.

Po pierwsze, oto trochę JSON do pracy:

let json = """"""let data = Data(json.utf8)

Ostatnia linia konwertuje go na obiekt Data, ponieważ z tym właśnie pracują dekodery Codable.

Następnie musimy zdefiniować strukturę Swift, która będzie przechowywać nasze gotowe dane:

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

Teraz możemy iść naprzód i wykonać dekodowanie:

let decoder = JSONDecoder()do { let decoded = try decoder.decode(.self, from: data) print(decoded.name)} catch { print("Failed to decode JSON")}

To wydrukuje „Paul”, który jest nazwą pierwszego użytkownika w JSON.

Konwersja case

Powszechnym problemem z JSON jest to, że użyje on innego formatowania dla swoich nazw kluczy niż chcemy użyć w Swift. Na przykład, możesz uzyskać „first_name” w swoim JSON i musisz przekonwertować to na właściwość firstName.

Teraz jednym z oczywistych rozwiązań jest po prostu zmiana JSON lub typów Swift, aby używały tej samej konwencji nazewnictwa, ale nie zamierzamy tego tutaj robić. Zamiast tego zamierzam założyć, że masz kod taki jak ten:

let json = """"""let data = Data(json.utf8)struct User: Codable { var firstName: String var lastName: String}

Aby to zadziałało, musimy zmienić tylko jedną właściwość w naszym dekoderze JSON:

let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCase

To instruuje Swift, aby mapować nazwy przypadków węża (names_written_like_this) na nazwy przypadków wielbłąda (namesWrittenLikeThis).

Mapowanie różnych nazw kluczy

Jeśli masz klucze JSON, które są zupełnie inne niż twoje właściwości Swift, możesz je mapować za pomocą CodingKeys enum.

Przyjrzyjrzyj się temu JSON:

let json = """"""

Te nazwy kluczy nie są świetne i naprawdę chcielibyśmy przekonwertować te dane na strukturę taką jak ta:

struct User: Codable { var firstName: String var lastName: String var age: Int}

Aby tak się stało, musimy zadeklarować CodingKeys enum: mapowanie, którego Codable może użyć do konwersji nazw JSON na właściwości dla naszej struktury. Jest to zwykły enum, który używa łańcuchów dla swoich surowych wartości, dzięki czemu możemy określić zarówno nazwę naszej właściwości (przypadek enum), jak i nazwę JSON (wartość enum) w tym samym czasie. Musi również być zgodny z protokołem CodingKey, co sprawia, że działa to z protokołem Codable.

Więc dodaj ten enum do struct:

enum CodingKeys: String, CodingKey { case firstName = "user_first_name" case lastName = "user_last_name" case age}

Teraz będzie w stanie zdekodować JSON zgodnie z planem.

Uwaga: enum nazywa się CodingKeys, a protokół nazywa się CodingKey.

Praca z datami ISO-8601

W internecie istnieje wiele sposobów pracy z datami, ale ISO-8601 jest najbardziej powszechny. Koduje on pełną datę w formacie RRRR-MM-DD, następnie literę „T” sygnalizującą początek informacji o czasie, następnie czas w formacie GG:MM:SS, a na końcu strefę czasową. Strefa czasowa „Z”, skrót od „Zulu time” jest powszechnie używana do oznaczania UTC.

Codable jest w stanie obsłużyć ISO-8601 z wbudowanym konwerterem daty. Tak więc, biorąc pod uwagę ten JSON:

let json = """"""

Możemy go zdekodować w ten sposób:

struct Baby: Codable { var firstName: String var timeOfBirth: Date}let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .iso8601

To umożliwia parsowanie daty ISO-8601, które konwertuje z 1999-04-03T17:30:31Z na instancję Date, jednocześnie obsługując konwersję wielkości węża na wielkość wielbłąda.

Praca z innymi popularnymi datami

Swift posiada wbudowaną obsługę trzech innych ważnych formatów dat. Używasz ich tak samo jak dat ISO-8601, jak pokazano powyżej, więc powiem o nich tylko krótko:

  • Format .deferredToDate jest własnym formatem daty Apple i śledzi liczbę sekund i milisekund od 1 stycznia 2001 roku. Nie jest to naprawdę użyteczne poza platformami Apple.
  • Format .millisecondsSince1970 śledzi liczbę sekund i milisekund od 1 stycznia 1970 roku. Jest to dość powszechne w sieci.
  • Format .secondsSince1970 śledzi liczbę całych sekund od 1 stycznia 1970 roku. Jest to bardzo powszechne w sieci, a drugie miejsce zajmuje ISO-8601.

Praca z niestandardowymi datami

Jeśli Twój format daty nie pasuje do jednej z wbudowanych opcji, nie rozpaczaj: Codable może parsować niestandardowe daty na podstawie utworzonego przez użytkownika formatu daty.

Na przykład, ten JSON śledzi dzień, w którym student ukończył uniwersytet:

let json = """"""

To używa formatu daty DD-MM-YYYY, który nie jest jedną z wbudowanych opcji Swifta. Na szczęście możesz dostarczyć wstępnie skonfigurowaną instancję DateFormatter jako strategię dekodowania daty, jak poniżej:

let formatter = DateFormatter()formatter.dateFormat = "dd-MM-yyyy"let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .formatted(formatter)

Praca z dziwnymi datami

Czasami otrzymasz daty tak dziwne, że nawet DateFormatter nie może sobie z nimi poradzić. Na przykład, możesz otrzymać JSON, który przechowuje daty używając liczby dni, które upłynęły od 1 stycznia 1970 roku:

let json = """"""

Aby to zadziałało, musimy napisać niestandardowy dekoder dla daty. Wszystko inne będzie nadal obsługiwane przez Codable – my tylko dostarczamy niestandardowe zamknięcie, które będzie przetwarzać część dat.

Możesz spróbować zrobić trochę hacky matematyki tutaj, takiej jak pomnożenie liczby dni przez 86400 (liczba sekund w dniu), a następnie użycie metody addTimeInterval() z Date. Jednak to nie uwzględni czasu letniego i innych problemów z datami, więc lepszym rozwiązaniem jest użycie DateComponents i Calendar jak poniżej:

let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .custom { decoder in // pull out the number of days from Codable let container = try decoder.singleValueContainer() let numberOfDays = try container.decode(Int.self) // create a start date of Jan 1st 1970, then a DateComponents instance for our JSON days let startDate = Date(timeIntervalSince1970: 0) var components = DateComponents() components.day = numberOfDays // create a Calendar and use it to measure the difference between the two let calendar = Calendar(identifier: .gregorian) return calendar.date(byAdding: components, to: startDate) ?? Date()}

Ostrzeżenie: Jeśli musisz parsować wiele dat, pamiętaj, że to zamknięcie będzie uruchamiane dla każdej z nich – spraw, by było szybkie!

Parsowanie danych hierarchicznych w prosty sposób

Każdy nietrywialny JSON prawdopodobnie będzie miał dane hierarchiczne – jedną kolekcję danych zagnieżdżoną wewnątrz innej. Na przykład:

let json = """"""

Codable jest w stanie sobie z tym poradzić, tak długo jak potrafisz jasno opisać zależności.

Jak dla mnie najłatwiejszym sposobem na zrobienie tego jest użycie zagnieżdżonych struktów, takich jak ten:

struct User: Codable { struct Name: Codable { var firstName: String var lastName: String } var name: Name var age: Int}

Wadą jest to, że jeśli chcesz odczytać imię użytkownika, musisz użyć user.name.firstName, ale przynajmniej faktyczna praca parsowania jest trywialna – nasz istniejący kod już działa!

Parsowanie danych hierarchicznych w trudny sposób

Jeśli chcesz sparsować dane hierarchiczne do płaskiej struktury – tzn, chcesz być w stanie napisać user.firstName zamiast user.name.firstName – to musisz samemu wykonać parsowanie. Nie jest to jednak zbyt trudne, a Codable czyni to pięknie bezpiecznym dla typów.

Po pierwsze, utwórz strukturę, z którą chcesz skończyć:

struct User: Codable { var firstName: String var lastName: String var age: Int}

Po drugie, musimy zdefiniować klucze kodowania, które opisują, gdzie dane można znaleźć w hierarchii.

Spójrzmy ponownie na JSON:

let json = """"""

Jak widać, w korzeniu znajduje się klucz o nazwie „imię” i inny o nazwie „wiek”, więc musimy dodać je jako nasze klucze kodowania korzenia. Umieść to wewnątrz swojego struct:

enum CodingKeys: String, CodingKey { case name, age}

Wewnątrz „name” znajdują się dwa kolejne klucze, „first_name” i „last_name”, więc stworzymy kilka kluczy kodujących dla tych dwóch. Dodaj to:

enum NameCodingKeys: String, CodingKey { case firstName, lastName}

Teraz trudna część: musimy napisać niestandardowy inicjalizator i niestandardową metodę kodowania dla naszego typu. Zacznij od dodania tej pustej metody do swojego struct:

init(from decoder: Decoder) throws {}

Wewnątrz niej, pierwszą rzeczą jaką musimy zrobić jest próba wyciągnięcia kontenera, który możemy odczytać używając kluczy naszego CodingKeys enum, tak jak to:

let container = try decoder.container(keyedBy: CodingKeys.self)

Gdy to zrobimy możemy spróbować odczytać naszą age właściwość. Odbywa się to w sposób bezpieczny dla typu: podajemy typ, który chcemy zdekodować (Int.self dla naszego wieku), wraz z nazwą klucza z enum CodingKeys:

age = try container.decode(Int.self, forKey: .age)

Następnie musimy zejść o jeden poziom w dół, aby odczytać nasze dane dotyczące nazwy. Jak widziałeś wcześniej, „name” jest kluczem najwyższego poziomu wewnątrz naszego enum CodingKeys, ale tak naprawdę jest to zagnieżdżony kontener innych wartości, które musimy odczytać wewnątrz. Tak więc, musimy wyciągnąć ten kontener:

let name = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)

I w końcu możemy odczytać dwa ciągi dla kluczy .firstName i .lastName:

firstName = try name.decode(String.self, forKey: .firstName)lastName = try name.decode(String.self, forKey: .lastName)

To kończy niestandardowy inicjalizator, ale wciąż mamy jeszcze jedną metodę do napisania: encode(to:). Jest to efektywnie odwrotność inicjalizatora, który właśnie napisaliśmy, ponieważ jej zadaniem jest przekonwertowanie naszych właściwości z powrotem na zagnieżdżony kontener, jak należy.

To oznacza utworzenie kontenera opartego na naszym enum CodingKeys i zapisanie tam age, a następnie utworzenie zagnieżdżonego kontenera opartego na naszym enum NameCodingKeys i zapisanie tam zarówno firstName, jak i lastName:

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

To kończy cały nasz kod. Mając to na miejscu, możesz teraz odczytać właściwość firstName bezpośrednio, co jest znacznie przyjemniejsze!

Hacking with Swift jest sponsorowany przez RevenueCat

SPONSOROWANE Budowanie i utrzymywanie infrastruktury subskrypcji w aplikacji jest trudne. Na szczęście jest na to lepszy sposób. Dzięki RevenueCat możesz wdrożyć subskrypcje dla swojej aplikacji w ciągu kilku godzin, a nie miesięcy, więc możesz wrócić do budowania swojej aplikacji.

Wypróbuj za darmo

Sponsoruj Hacking with Swift i dotrzyj do największej na świecie społeczności Swift!

Leave a Reply