JSONSerialization — wbudowana klasa iOS z frameworka Foundation, przeznaczona do konwersji JSON na obiekty Foundation i odwrotnie. To API jest podstawowym mechanizmem pracy z JSON na platformach Apple bez podłączania zewnętrznych bibliotek, obsługując parsowanie słowników, tablic i typów prymitywnych. Według Apple Developer, 2024, JSONSerialization obsługuje pracę z Data, strumieniami i opcjami odczytu dla elastycznego przetwarzania danych JSON.
Najważniejsze
JSONSerialization — to klasa z frameworka Foundation, dostępna na iOS, macOS, tvOS i watchOS. Udostępnia metody do konwersji danych JSON na obiekty Foundation (NSDictionary, NSArray, NSString, NSNumber) i odwrotnie. Klasa pojawiła się w iOS 5 i do wprowadzenia Codable (Swift 4) pozostawała głównym sposobem pracy z JSON na platformach Apple. Pomimo wieku, JSONSerialization pozostaje popularny w legacy-projektach na Objective-C oraz w scenariuszach wymagających dynamicznego przetwarzania JSON bez stałego schematu modelu.
Pomimo pojawienia się Codable, JSONSerialization pozostaje aktualny w kilku scenariuszach. Dynamiczna struktura JSON — gdy format odpowiedzi zmienia się lub jest z góry nieznany — wymaga dostępu do słowników przez klucze, co łatwiej zrobić przez JSONSerialization. Klasa jest również stosowana w projektach Objective-C, gdzie Codable jest niedostępny, oraz przy pracy ze strumieniami do etapowego parsowania dużych plików JSON. W testach i makietach isValidJSONObject i data(withJSONObject:options:) pozwalają szybko generować JSON-fixtury bez podłączania zewnętrznych bibliotek, co przyspiesza rozwój i prototypowanie.
import Foundation
// Podstawowa struktura użycia JSONSerialization
let jsonString = """
{
"id": 1,
"name": "John Doe",
"email": "john@example.com"
}
"""
guard let jsonData = jsonString.data(using: .utf8) else {
return
}
do {
let json = try JSONSerialization
.jsonObject(with: jsonData,
options: .mutableContainers)
print(json)
} catch {
print("Błąd parsowania JSON: \(error)")
}
JSONSerialization udostępnia cztery główne metody do pracy z JSON. Główna metoda — jsonObject(with:options:), która konwertuje Data na obiekty Foundation. Metoda data(withJSONObject:options:) wykonuje serializację zwrotną. isValidJSONObject(_:) sprawdza, czy obiekt może być serializowany. writeJSONObject(_:to:options:error:) zapisuje JSON bezpośrednio do strumienia. Do odczytu JSON z InputStream istnieje metoda jsonObject(with:options:), przyjmująca strumień zamiast Data, co jest wygodne przy integracji z zapytaniami sieciowymi zwracającymi dane strumieniowe.
Metoda jsonObject przyjmuje Data i zwraca Any — zazwyczaj NSDictionary lub NSArray. Do bezpiecznej pracy wynik jest rzutowany na oczekiwany typ przez rzutowanie warunkowe. Metoda data przyjmuje obiekt Foundation i zwraca Data z reprezentacją JSON. Opcja .prettyPrinted dodaje formatowanie z wcięciami dla czytelności.
let jsonString = """
{
"products": [
{"id": 1, "name": "iPhone", "price": 999},
{"id": 2, "name": "iPad", "price": 799}
]
}
"""
let data = Data(jsonString.utf8)
if let json = try? JSONSerialization
.jsonObject(with: data) as? [String: Any],
let products = json["products"] as? [[String: Any]] {
for product in products {
if let name = product["name"] as? String {
print("Produkt: \(name)")
}
}
}
// Deserializacja zwrotna: obiekt -> JSON
let outputDict: [String: Any] = ["status": "ok", "count": 42]
if let outputData = try? JSONSerialization
.data(withJSONObject: outputDict,
options: .prettyPrinted) {
String(data: outputData, encoding: .utf8)
}
Podstawowe parsowanie słownika z typami prymitywnymi — to najczęstsza operacja z JSONSerialization. Po otrzymaniu Data przez URLSession programista wywołuje jsonObject i rzutuje wynik na oczekiwany typ. Dla tablic obiektów stosuje się rzutowanie na [[String: Any]], po czym każdy element jest przetwarzany w pętli. Takie podejście jest elastyczne, ale wymaga ręcznego zarządzania typami.
Rzeczywiste API zwracają złożone zagnieżdżone obiekty JSON z tablicami, datami i polami opcjonalnymi. JSONSerialization poprawnie obsługuje dowolny poziom zagnieżdżenia, ale programista musi samodzielnie rzutować każdy poziom na wymagany typ. Aby uprościć to zadanie, Apple zaleca stosowanie Codable dla danych typowanych, a JSONSerialization — tylko dla struktur dynamicznych.
// Parsowanie odpowiedzi z API
func parseUserResponse(data: Data) {
do {
guard let json = try JSONSerialization
.jsonObject(with: data) as? [String: Any]
else { return }
guard let userId = json["id"] as? Int,
let name = json["name"] as? String
else {
throw ParsingError.missingField
}
print("Użytkownik: \(name) (ID: \(userId))")
} catch let error as ParsingError {
print("Parsowanie nie powiodło się: \(error)")
} catch {
print("Nieoczekiwany błąd: \(error)")
}
}
enum ParsingError: Error {
case missingField
case invalidType
}
JSONSerialization zgłasza błędy przy nieprawidłowym JSON, niezgodności typów lub przekroczeniu głębokości zagnieżdżenia. Błędy należą do typu CocoaError i zawierają kod z opisem problemu. Programista jest zobowiązany do ich obsługi przez konstrukcję do-catch, w przeciwnym razie aplikacja zakończy się awaryjnie. Najczęstsze błędy: NSPropertyListReadCorruptError (nieprawidłowy JSON) i NSPropertyListReadUnknownError. Każdy typ błędu wymaga własnej strategii obsługi: przy nieprawidłowym formacie należy zażądać ponownego wysłania danych, a przy niezgodności struktury — zaktualizować model parsowania.
Nieprawidłowy JSON — najczęstsza przyczyna awarii: brakujący przecinek, zbędny znak lub nieprawidłowo escaped cudzysłów łamią całe parsowanie. Drugi typ błędów — niezgodność z oczekiwaną strukturą: na przykład serwer zwrócił tablicę zamiast słownika. JSONSerialization.fragmentsAllowed pozwala odczytywać JSON, którego korzeniem nie jest słownik lub tablica, ale wartość prymitywna. Programista może również napotkać błąd przekroczenia głębokości zagnieżdżenia, gdy JSON zawiera zbyt wiele poziomów hierarchii.
JSONSerialization udostępnia kilka opcji do konfiguracji parsowania. .mutableContainers zwraca NSMutableDictionary i NSMutableArray zamiast niezmiennych wersji, co jest przydatne przy modyfikacji danych po parsowaniu. .mutableLeaves czyni modyfikowalnymi wartości tekstowe. .fragmentsAllowed zezwala na JSON, którego korzeniem nie jest obiekt lub tablica, a tekst lub liczba — to wygodne dla prostych odpowiedzi API. Opcje .withoutEscapingSlashes i .sortedKeys są dostępne dla metody data(withJSONObject:options:), sterując formatowaniem serializowanego JSON. Opcje są przekazywane maską bitową, co pozwala łączyć wiele wartości przez operator | dla elastycznej konfiguracji parsowania.
// Obsługa różnych typów błędów
func safeParse(jsonData: Data) {
do {
let object = try JSONSerialization
.jsonObject(with: jsonData,
options: .fragmentsAllowed)
if let dictionary = object as? [String: Any] {
print("Słownik z \(dictionary.count) kluczami")
} else if let array = object as? [Any] {
print("Tablica z \(array.count) elementami")
}
} catch CocoaError.propertyListReadCorrupt {
print("Uszkodzone dane JSON")
} catch let error as CocoaError {
print("Błąd Cocoa: \(error)")
} catch {
print("Nieznany błąd: \(error)")
}
}
// Sprawdzanie poprawności obiektu przed serializacją
let testObject: [String: Any] = ["key": "value", "nested": ["a": 1]]
if JSONSerialization.isValidJSONObject(testObject) {
print("Poprawny obiekt JSON")
}
Na wydajność JSONSerialization wpływa rozmiar danych i częstotliwość wywołań. Przy jednokrotnym parsowaniu małej odpowiedzi serwera różnica jest niezauważalna, ale przy przetwarzaniu dziesiątek megabajtów JSON lub częstych wywołaniach w pętlach warto uwzględnić narzut na rzutowanie typów. JSONSerialization działa synchronicznie w bieżącym wątku, dlatego dla dużych dokumentów zaleca się przeniesienie parsowania do kolejki tła przez DispatchQueue.global(). Alternatywnie można użyć InputStream do przetwarzania strumieniowego bez ładowania całego pliku do pamięci, co jest krytyczne dla aplikacji z ograniczonymi zasobami. Do zapisu JSON do pliku lub strumienia sieciowego metoda writeJSONObject(_:to:options:error:) pozwala bezpośrednio kierować serializowane dane do OutputStream bez tworzenia pośredniego obiektu Data, co zmniejsza zużycie pamięci przy pracy z dużymi dokumentami.
Często zadawane pytania
JSONSerialization — to klasa Foundation do konwersji danych JSON na obiekty Foundation (NSDictionary, NSArray) i odwrotnie. Działa na iOS, macOS, tvOS i watchOS bez podłączania dodatkowych bibliotek.
Codable — to protokół Swift do automatycznej typowanej serializacji, który kompiluje się w typowo bezpieczny kod. JSONSerialization pracuje z dynamicznymi typami Any i wymaga ręcznego rzutowania. Codable jest preferowany dla nowych projektów, JSONSerialization — dla Objective-C i danych dynamicznych.
Użyj konstrukcji do-catch przy wywołaniu jsonObject. Błędy JSONSerialization należą do CocoaError. Do debugowania sprawdzaj NSPropertyListReadCorruptError, który wskazuje na nieprawidłowy format danych JSON.
Tak, JSONSerialization obsługuje dowolny poziom zagnieżdżenia słowników i tablic. Wszystkie zagnieżdżone obiekty są konwertowane na odpowiadające typy Foundation (NSDictionary, NSArray, NSString, NSNumber), zachowując oryginalną strukturę JSON.
JSONSerialization jest odpowiedni przy dynamicznej strukturze JSON, w projektach Objective-C, przy pracy ze strumieniami i do sprawdzania poprawności JSON przez isValidJSONObject. Dla typowanych struktur ze znanym schematem preferowany jest Codable.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również