Written by Reinder de Vries on January 2021 in App Development, iOS, Swift
Możesz użyć Codable
w Swift do kodowania i dekodowania niestandardowych formatów danych, takich jak JSON, do natywnych obiektów Swift. Niewiarygodnie łatwo jest mapować obiekty Swift na dane JSON i odwrotnie, po prostu przyjmując protokół Codable
.
Jako pragmatyczny programista iOS, natkniesz się na JSON raczej wcześniej niż później. Każda usługa sieciowa, od Facebooka po Foursquare, używa formatu JSON, aby uzyskać dane do i z Twojej aplikacji. Jak skutecznie kodować i dekodować dane JSON do obiektów Swift?
W tym poradniku dowiesz się, jak pracować z obiektami JSON w Swift, używając protokołu Codable
. To, czego się nauczysz, może być łatwo zastosowane również do innych formatów danych. Zajmiemy się kodowaniem i dekodowaniem za pomocą JSONEncoder
i JSONDecoder
, a także pokażę ci, jak pośredniczyć między JSON a danymi Swift.
Gotowy? Let’s go.
- Get Started: Encoding and Decoding
- The Codable Protocol Explained
- Working with Codable
- Decoding JSON to Swift Objects with Codable
- Encoding Swift Objects as JSON with Codable
- Working with Nested Arrays and Codable
- Further Reading
Get Started: Encoding and Decoding
Jaki problem właściwie rozwiązuje protokół Codable
w Swift? Zacznijmy od przykładu.
Wyobraźmy sobie, że budujesz aplikację z przepisami kulinarnymi. Aplikacja pokazuje różne przepisy na liście, w tym składniki, instrukcje i podstawowe informacje o żywności.
Dane do aplikacji pobierasz z usługi internetowej i ich API w chmurze. Ten interfejs API używa formatu danych JSON. Za każdym razem, gdy żądasz przepisu z usługi internetowej, otrzymujesz z powrotem dane JSON.
Tutaj znajduje się przykład danych JSON dla przepisu:
{ "name": "Spaghetti Bolognese", "author": "Reinder's Cooking Corner", "url": "https://cookingcorner.com/spaghetti-bolognese", "yield": 4, "ingredients": , "instructions": "Cook spaghetti, fry beef and garlic, add tomatoes, add love, eat!"}
Spójrz na strukturę danych JSON.
Obiekty JSON są zawinięte w nawiasy {
i }
, a tablice są zawinięte w nawiasy kwadratowe . Nazwy właściwości są ciągami znaków, zawiniętymi w cudzysłów
"
. Wartości w JSON mogą być ciągami znaków, liczbami (bez cudzysłowów), tablicami lub innymi obiektami. Można również zagnieżdżać dane, tj. tablice w tablicach, obiekty w tablicach itp. w celu utworzenia złożonej hierarchii danych.
JSON jest tekstowym formatem danych, którego używa wiele usług internetowych, w tym API Twittera, Facebooka, Foursquare’a itd. Jeśli budujesz aplikacje, które korzystają z zasobów internetowych, natkniesz się na JSON.
Format JSON jest lepszy niż XML, powszechna alternatywa, ponieważ jest wydajny, łatwo parsowany i czytelny dla ludzi. JSON jest uzgodnionym formatem dla usług sieciowych, interfejsów API i aplikacji. Jest używany w całej sieci, aplikacjach i usługach online, ponieważ format jest prosty i elastyczny.
I JSON ma jedną wspaniałą zdolność: można zakodować dowolny format danych w JSON, i dekodować JSON z powrotem do dowolnego formatu danych. Ten proces kodowania i dekodowania jest tym, co czyni JSON tak potężnym.
Możesz wziąć swoje wartości Swift String
, Int
, Double
, URL
, Date
, Data
, Array
i Dictionary
i zakodować je jako JSON. Następnie wysyłasz je do usługi sieciowej, która dekoduje wartości do natywnego formatu, który rozumie. Podobnie, usługa sieciowa wysyła dane zakodowane jako JSON do twojej aplikacji, a ty dekodujesz dane do natywnych typów, takich jak String
, Double
i Array
.
Gdy twoja aplikacja recepturowa otrzymuje JSON (patrz wyżej), może być następnie zdekodowana do struktury Swift, takiej jak ta:
struct Recipe { var name: String var author: String var url: URL var yield: Int var ingredients: var instructions: String}
W Swift, protokół Codable
jest używany do przejścia z obiektu danych JSON do rzeczywistej klasy Swift lub struktury. Nazywa się to dekodowaniem, ponieważ dane JSON są dekodowane do formatu, który rozumie Swift. Działa to również w drugą stronę: kodowanie obiektów Swift jako JSON.
Protokół Codable
w Swift jest w rzeczywistości aliasem protokołów Decodable
i Encodable
. Ponieważ często używasz kodowania i dekodowania razem, używasz protokołu Codable
, aby uzyskać oba protokoły za jednym zamachem.
Centralnym punktem przepływu pracy kodowania/dekodowania jest protokół Swift Codable
. Dowiedzmy się, jak to działa, następny!
Nie możesz odróżnić „kodowania” i „dekodowania” od siebie? Pomyśl o tym w ten sposób: Konwertujemy dane z i do „kodu”, jak maszyna Enigma lub tajny szyfr kryptograficzny. Kodowanie oznacza przekształcanie danych w kod; en-kodowanie, czyli „w/w kodzie”. Dekodowanie oznacza konwersję kodu na dane; de-kodowanie, czyli „z/do kodu”.
Zatrudnij się jako programista iOS
Naucz się budować aplikacje iOS 14 za pomocą Swift 5
Zapisz się na mój kurs rozwoju iOS i dowiedz się, jak rozpocząć karierę jako profesjonalny programista iOS.
Protokół Codable wyjaśniony
Używanie JSON przed Swiftem 4 było trochę kłopotliwe. Musiałbyś sam serializować JSON za pomocą JSONSerialization
, a następnie rzutować każdą właściwość JSON na odpowiedni typ Swift.
let json = try? JSONSerialization.jsonObject(with: data, options: )if let recipe = json as? { if let yield = recipe as? Int { recipeObject.yield = yield }}
Powyższy snippet pobiera tylko wartość yield
z JSON i rzutuje ją na Int
. Jest to bardzo czasochłonne i ciężko jest poprawnie reagować na potencjalne błędy i rozbieżności typów. Mimo że to działa, nie jest idealne.
Biblioteki takie jak SwiftyJSON znacznie ułatwiają pracę z JSON, ale nadal musisz mapować dane JSON do odpowiednich obiektów i właściwości Swift.
W Swift 4 i nowszych, możesz zamiast tego użyć protokołu Codable
. Twoja struktura lub klasa Swift musi jedynie przyjąć protokół Codable
, a otrzymasz kodowanie i dekodowanie JSON po wyjęciu z pudełka, za darmo.
Protokół Codable
jest kompozycją dwóch protokołów, Decodable i Encodable. Oba protokoły są dość minimalne; definiują one tylko funkcje init(from: Decoder)
i encode(to: Encoder)
, odpowiednio. Innymi słowy, funkcje te oznaczają, że aby typ był „dekodowalny” lub „kodowalny”, będzie musiał „dekodować z czegoś” i „kodować do czegoś”.
Prawdziwa magia Codable
dzieje się z protokołami Decoder
i Encoder
. Protokoły te są przyjmowane przez komponenty, które kodują / dekodują różne formaty danych, takie jak JSON. W Swift mamy kilka -koderów:
-
PropertyListEncoder
iPropertyListDecoder
dla list właściwości .plist -
JSONEncoder
iJSONDecoder
dla JSON – to my! - NSKeyedArchiver może pracować z
Codable
, przezPropertyListEncoder
,Data
iNSCoding
Klasy JSONDecoder
i JSONEncoder
używają tych protokołów Decoder
i Encoder
, aby zapewnić funkcjonalność dekodowania / kodowania JSON. Oznacza to również, że możesz napisać swój własny niestandardowy koder/dekoder dla Codable
, pod warunkiem, że przyjmiesz protokoły Decoder
i Encoder
!
Potrzebujesz odświeżenia na temat protokołów w Swift? Przeczytaj Protocols In Swift Explained, aby nadrobić zaległości.
Praca z Codable
Przyjrzyjrzyjmy się przykładowi. Zamierzamy zmapować niektóre dane JSON do struktury Swift. Zasadniczo dekodujemy JSON do rzeczywistego obiektu Swift.
Po pierwsze, tworzymy struct o nazwie User
. Tak jak poniżej:
struct User: Codable { var first_name: String var last_name: String var country: String}
Ten User
struct ma trzy proste właściwości typu String
i jest zgodny z protokołem Codable
.
Następnie napiszmy trochę JSON-u. Oto JSON, z którym będziemy pracować:
{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}
Dane JSON są zazwyczaj wprowadzane do aplikacji jako odpowiedź na żądanie usługi sieciowej lub poprzez plik .json
, ale w tym przykładzie po prostu umieścimy JSON w pliku String
. W ten sposób:
let jsonString = """{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}"""
Uwaga: Powyższy kod używa potrójnego cudzysłowu """
do tworzenia ciągów wielowierszowych. Neat!
To, co robimy dalej, to dekodowanie JSON i przekształcenie go w obiekt User
. Tak jak poniżej:
let jsonData = jsonString.data(using: .utf8)!let user = try! JSONDecoder().decode(User.self, from: jsonData)print(user.last_name)// Output: Doe
Oto co się dzieje:
- Po pierwsze, zamieniasz
jsonString
w obiektData
poprzez wywołanie funkcjidata(using:)
na łańcuchu. Jest to niezbędny krok pośredni. - Następnie tworzysz obiekt
JSONDecoder
i natychmiast wywołujesz na nim funkcjędecode(_:from:)
. To zamieniajsonData
w obiekt typuUser
, poprzez dekodowanie JSON. TypUser.self
jest przekazywany jako parametr. - Na koniec wypisujesz ostatnie nazwisko użytkownika za pomocą
print(user.last_name)
. Tauser
wartość maUser
jako swój typ, więc jest to rzeczywisty obiekt Swift!
Łatwe, prawda? Zasadniczo „zmapowałeś” obiekt JSON do struktury Swift i zdekodowałeś format JSON do natywnego obiektu, z którym Swift może pracować.
W powyższym kodzie ignorujemy wszelkie błędy rzucone przez decode(_:from:)
z try!
. W swoim własnym kodzie będziesz musiał obsłużyć błędy za pomocą bloku do-try-catch
. Błąd, który może zostać rzucony, pochodzi na przykład z dostarczenia nieprawidłowego JSON.
Blok User.self
jest metatypem, odniesieniem do samego typu User
. Mówimy dekoderowi, że chcemy zdekodować dane z User
struct, poprzez dostarczenie mu referencji do tego typu.
Powszechnym błędem podczas definiowania typu Codable
, jak User
struct, jest niedopasowanie kluczy i właściwości. Jeśli dodałeś właściwość do swojego struct, która nie jest obecna w JSON lub ma inny typ, otrzymasz błąd podczas dekodowania JSON. Odwrotna sytuacja, tj. właściwość, która nie istnieje w twoim struct, ale istnieje w JSON, nie jest problemem. Najłatwiej jest debugować to po jednej właściwości na raz, tj. Kontynuuj dodawanie właściwości, aż JSON nie będzie już dekodowany bez błędów. Możesz również określić, jakie właściwości/klucze włączyć lub zignorować za pomocą CodingKeys
enum (patrz poniżej).
Dekodowanie JSON do obiektów Swift z Codable
Przyjrzyjrzyjmy się jeszcze raz przykładowi z poprzedniej sekcji i rozwińmy go. Oto JSON, z którym pracujemy:
let jsonString = """{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}"""
Zamieniamy go następnie w obiekt Data
. W ten sposób:
let jsonData = jsonString.data(using: .utf8)!
Jest to niezbędny krok pośredni. Zamiast reprezentować JSON jako ciąg znaków, przechowujemy teraz JSON jako natywny obiekt Data
. Kodowanie znaków, którego używamy dla tego łańcucha, to UTF8.
Jeśli przyjrzysz się uważnie, zobaczysz, że powyższy kod używa force unwrapping do pracy z opcjonalną wartością zwracaną z data(using:)
. Rozwińmy tę opcjonalność bardziej elegancko!
if let jsonData = jsonString.data(using: .utf8) { // Use `jsonData`} else { // Respond to error }
Z powyższym kodem możemy reagować na błędy, gdy data(using:)
zwraca nil
. Można na przykład wyświetlić komunikat o błędzie lub po cichu pozwolić, aby zadanie zakończyło się niepowodzeniem i zapisać informacje diagnostyczne w logu.
Następnie tworzymy nowy obiekt JSONDecoder
. Tak jak to:
let decoder = JSONDecoder()
Następnie używamy tego dekodera do dekodowania danych JSON. Like this:
let user = try! decoder.decode(User.self, from: jsonData)
Jednakże, funkcja decode(_:from:)
może wyrzucać błędy. Powyższy kod ulega awarii, gdy tylko tak się stanie. Ponownie, chcemy reagować na wszelkie błędy, które mogą się zdarzyć. Na przykład tak:
do { let user = try decoder.decode(User.self, from: jsonData) print(user.last_name)} catch { print(error.localizedDescription)}
I cały blok kodu wygląda teraz tak, jak poniżej. Widzisz, jaka jest różnica?
if let jsonData = jsonString.data(using: .utf8){ let decoder = JSONDecoder() do { let user = try decoder.decode(User.self, from: jsonData) print(user.last_name) } catch { print(error.localizedDescription) }}
Nigdy nie wyciszaj błędów. Wyłapuj błąd i reaguj na niego, albo za pomocą UI/UX, przez ponowienie próby wykonania zadania, albo przez rejestrowanie, rozbicie i naprawienie go.
Praca z CodingKeys
Co jeśli nasze właściwości JSON, takie jak first_name
i/lub firstName
, są inne niż właściwości Swift struct? Tam właśnie pojawia się CodingKeys
.
Każda klasa lub struct, która jest zgodna z Codable
, może zadeklarować specjalne zagnieżdżone wyliczenie o nazwie CodingKeys
. Używasz jej do definiowania właściwości, które powinny być kodowane i dekodowane, łącznie z ich nazwami.
Przyjrzyjrzyjmy się przykładowi. W poniższym User
struct zmieniliśmy nazwy właściwości z snake_case na camelCase. Na przykład, klucz first_name
nazywa się teraz firstName
.
struct User:Codable { var firstName: String var lastName: String var country: String enum CodingKeys: String, CodingKey { case firstName = "first_name" case lastName = "last_name" case country }}
Kiedy użyjesz powyższego structu z przykładami z poprzednich sekcji, zobaczysz, że możesz użyć obiektu User
z nowymi nazwami właściwości. Tak jak to:
print(user.firstName)// Output: Johnprint(user.country)// Output: United Kingdom
Wyliczenie CodingKeys
mapuje swoje przypadki do właściwości i używa wartości łańcuchowych do wybrania odpowiednich nazw właściwości w danych JSON. Przypadek w enum to nazwa właściwości, której chcesz użyć w struct, a jej wartość to nazwa klucza w danych JSON.
Kodowanie obiektów Swift jako JSON z Codable
Do tej pory skupiliśmy się na dekodowaniu danych JSON do obiektów Swift. A co z przejściem w drugą stronę? Czy możemy również kodować obiekty jako JSON? Tak, dlaczego nie!
var user = User()user.firstName = "Bob"user.lastName = "and Alice"user.country = "Cryptoland"let jsonData = try! JSONEncoder().encode(user)let jsonString = String(data: jsonData, encoding: .utf8)!print(jsonString)
A wyjście to:
{"country":"Cryptoland","first_name":"Bob","last_name":"and Alice"}
Co się tutaj dzieje?
- Po pierwsze, tworzysz obiekt
User
i przypisujesz pewne wartości do jego właściwości - Następnie używasz
encode(_:)
do zakodowania obiektuuser
do obiektu JSONData
- W końcu, konwertujesz obiekt
Data
naString
i drukujesz go
Jeśli przyjrzysz się uważnie, zobaczysz, że kodowanie podąża za tymi samymi krokami co dekodowanie, z wyjątkiem odwrotności. Przechodzimy od obiektu Swift, przez dekoder, w wyniku czego otrzymujemy ciąg JSON.
Jak poprzednio, czy możemy rozszerzyć przykład, aby poradzić sobie z błędami? Tak! Na przykład tak:
let encoder = JSONEncoder()encoder.outputFormatting = .prettyPrinteddo { let jsonData = try encoder.encode(user) if let jsonString = String(data: jsonData, encoding: .utf8) { print(jsonString) }} catch { print(error.localizedDescription)}
W powyższym przykładzie używamy również właściwości outputFormatting
kodera, aby „ładnie wydrukować” dane JSON. To dodaje spacje, tabulatory i nowe linie, aby uczynić ciąg JSON łatwiejszym do odczytania. Na przykład tak:
{ "country" : "Cryptoland", "first_name" : "Bob", "last_name" : "and Alice"}
Awesome!
Warto zauważyć, że słowniki w Swift, a także w JSON, nie mają ustalonej kolejności sortowania. Dlatego zobaczysz różne kolejności sortowania dla kluczy country
, first_name
, itp. w JSON, jeśli uruchomisz powyższy kod kilka razy. Większość usług internetowych, które wyprowadzają JSON, wymusi stałą kolejność sortowania poprzez schemat, co zdecydowanie ułatwia czytanie i nawigację po JSON.
Praca z zagnieżdżonymi tablicami i Codable
Do tej pory pracowaliśmy z prostymi obiektami JSON – po prostu klasa z kilkoma właściwościami. Ale co, jeśli dane, z którymi pracujesz, są bardziej złożone?
Na przykład, dane JSON, które możesz otrzymać z powrotem z serwisu Twittera lub Facebooka, są często zagnieżdżone. To znaczy, dane, do których chcesz się dostać, są pochowane w 2-3 poziomach tablic JSON i słowników. Co teraz?!?
Popatrzmy najpierw na prosty przykład. Oto struktura Swift, z którą będziemy pracować:
struct User: Codable { var first_name: String var last_name: String}
A oto dane JSON, które chcemy zdekodować:
let jsonString = """"""
Jeśli przyjrzysz się uważnie, zobaczysz, że dane, których potrzebujemy, są przechowywane jako tablica. JSON nie jest jednym prostym obiektem, ale jest to tablica z 3 obiektami User
. Wiemy, że jest to tablica obiektów, dzięki nawiasom kwadratowym (tablica) i nawiasom kwadratowym
{ }
(obiekt). Wielokrotne elementy są zawsze oddzielane przecinkami.
Jak możemy rozszyfrować te User
obiekty? Oto jak:
let jsonData = jsonString.data(using: .utf8)!let users = try! JSONDecoder().decode(.self, from: jsonData)for user in users { print(user.first_name)}
Powyższy fragment kodu jest bardzo podobny do tego, co widziałeś wcześniej, ale jest jedna ważna różnica. Widzisz typ, który przekazujemy do funkcji decode(_:from:)
? Typem tym jest (z .self), czyli „tablica obiektów użytkownika”. Zamiast pracować z jednym obiektem
User
, chcemy zdekodować kilka z nich, a to jest oznaczone typem tablicowym .
Następnie omówimy, jak można pracować z bardziej złożonymi typami zagnieżdżonymi. Rozważmy na przykład, że tablica User
obiektów jest zagnieżdżona wewnątrz słownika JSON. W ten sposób:
let jsonString = """{ "users": }"""
W powyższym wycinku JSON, elementem najwyższego poziomu jest słownik (lub „obiekt”). Ma on tylko jedną parę klucz-wartość: tablicę users
. Dane, które chcemy uzyskać, znajdują się wewnątrz tej tablicy. Jak się do nich dostać?
Ważne jest, aby nasze podejście nie było zbyt skomplikowane. Łatwo jest pomyśleć, że będziesz potrzebował zaawansowanego parsowania JSON, ale jak się okazuje, możemy również zagnieżdżać struktury Swift. Zamierzamy opisać cały JSON jako struct, z User
struct wewnątrz niego.
Tutaj, sprawdź to:
struct Response: Codable{ struct User: Codable { var first_name: String var last_name: String } var users: }
Oto co się dzieje:
- Górnego poziomu słownik JSON odpowiada typowi
Response
. Tak jak poprzednio, typ ten jest zgodny zCodable
, aby wspierać kodowanie i dekodowanie JSON. - Wewnątrz tej struktury zdefiniowaliśmy inną strukturę o nazwie
User
. Jest to dokładnie ten sam typ, którego używaliśmy wcześniej. Ten struct jest „zagnieżdżony”. - Ta
Response
struct ma jedną właściwość o nazwieusers
typu, czyli tablicę obiektów User.
Fajną rzeczą jest to, że semantycznie zarówno struktury danych JSON, jak i Swift są dokładnie takie same. Po prostu używają innej składni. Zdefiniowaliśmy zagnieżdżoną tablicę wewnątrz słownika najwyższego poziomu, tak jak Response
struct ma zagnieżdżoną User
struct i users
właściwość wewnątrz niej.
Sprawianie, że to działa, jest teraz bułką z masłem. Sprawdź to:
let jsonData = jsonString.data(using: .utf8)!let response = try! JSONDecoder().decode(Response.self, from: jsonData)for user in response.users { print(user.first_name)}
Widzisz, jak używamy typu Response
do dekodowania JSON, a następnie pętli nad właściwością response.users
? Sprawdź to z Response
struct, oraz z JSON. Wybieramy users
parę klucz-wartość w słowniku najwyższego poziomu, i mapujemy obiekty wewnątrz do User
obiektów. Neat!
Używanie typów zagnieżdżonych jest świetnym podejściem ogólnego przeznaczenia do złożonych struktur danych JSON. Kiedy natkniesz się na kawałek JSON, którego nie możesz łatwo reprezentować w namacalnym typie, takim jak User
lub Tweet
, spróbuj rozszerzyć typ do czegoś takiego jak Response
lub UserCollection
. Zamiast iść głębiej, idź szerzej i włącz cały JSON. Użyj typów zagnieżdżonych, aby dostać się do danych, które chcesz. Warto również zauważyć, że nie musisz dodawać User
struct w Response
struct – może on również wyjść poza niego.
Zatrudnij się jako programista iOS
Naucz się budować aplikacje iOS 14 za pomocą Swift 5
Zapisz się na mój kurs rozwoju iOS i dowiedz się, jak rozpocząć karierę jako profesjonalny programista iOS.
Dalsze lektury
I to już wszystko! Teraz już wiesz:
- Jak używać
Codable
do tworzenia obiektów, które mogą być kodowane i dekodowane - Jak używać
JSONDecoder
iJSONEncoder
do kodowania i dekodowania obiektów JSON i ich odpowiedników w języku Swift - Do czego służy kodowanie i dekodowanie, i dlaczego jest to ważne w codziennym rozwoju iOS
- Jak pracować z bardziej złożonymi danymi JSON i jak używać typów zagnieżdżonych
Chcesz dowiedzieć się więcej? Sprawdź te zasoby:
- Arrays, Dictionaries and Structs
- Working With JSON In Swift With SwiftyJSON
- Introduction Of Object-Oriented Programming In Swift
- How To: Find An Item In An Array In Swift
- How To Use Apple’s Developer Documentation For Fun And Profit
- How To Find Strings With Regular Expressions In Swift
- Why App Architecture Matters
- Strings in Swift Explained
- Working with Files on iOS with Swift
- Storing Data with NSCoding and NSKeyedArchiver
.