Swift 4 dodał nowy Codable
protokół. Kiedy używam JSONDecoder
, wydaje się, że wszystkie nieopcjonalne właściwości mojej Codable
klasy mają klucze w formacie JSON lub generuje błąd.
Nadanie każdej właściwości mojej klasy opcjonalności wydaje się niepotrzebnym kłopotem, ponieważ naprawdę chcę użyć wartości w json lub wartości domyślnej. (Nie chcę, aby ta właściwość była zerowa).
Czy jest na to sposób?
class MyCodable: Codable {
var name: String = "Default Appleseed"
}
func load(input: String) {
do {
if let data = input.data(using: .utf8) {
let result = try JSONDecoder().decode(MyCodable.self, from: data)
print("name: \(result.name)")
}
} catch {
print("error: \(error)")
// `Error message: "Key not found when expecting non-optional type
// String for coding key \"name\""`
}
}
let goodInput = "{\"name\": \"Jonny Appleseed\" }"
let badInput = "{}"
load(input: goodInput) // works, `name` is Jonny Applessed
load(input: badInput) // breaks, `name` required since property is non-optional
Odpowiedzi:
Podejście, które preferuję to tzw. DTO - obiekt transferu danych. Jest to struktura zgodna z Codable i reprezentująca żądany obiekt.
Następnie po prostu inicjujesz obiekt, którego chcesz użyć w aplikacji z tym DTO.
Takie podejście jest również dobre, ponieważ możesz zmienić nazwę i zmienić ostateczny obiekt, jak chcesz. Jest przejrzysty i wymaga mniej kodu niż ręczne dekodowanie. Co więcej, dzięki temu podejściu można oddzielić warstwę sieciową od innych aplikacji.
źródło
Możesz zaimplementować
init(from decoder: Decoder)
metodę w swoim typie zamiast używać domyślnej implementacji:Możesz również utworzyć
name
stałą właściwość (jeśli chcesz):lub
Dodaj komentarz: z niestandardowym rozszerzeniem
możesz zaimplementować metodę init jako
ale to niewiele krócej niż
źródło
CodingKeys
wyliczenia (aby usunąć niestandardową definicję) :)ObjectMapper
radzi sobie z tym bardzo ładnie.Decodable
i zapewniasz również własną implementacjęinit(from:)
? W takim przypadku kompilator zakłada, że chcesz ręcznie obsługiwać dekodowanie i dlatego nie syntetyzujeCodingKeys
za Ciebie wyliczenia. Jak mówisz, dostosowywanie się doCodable
zamiast tego działa, ponieważ teraz kompilator syntetyzujeencode(to:)
za Ciebie, a więc również syntetyzujeCodingKeys
. Jeśli podasz również własną implementacjęencode(to:)
,CodingKeys
nie będzie już syntetyzowana.Jednym z rozwiązań byłoby użycie obliczonej właściwości, która domyślnie przyjmuje żądaną wartość, jeśli klucz JSON nie zostanie znaleziony. Dodaje to dodatkową szczegółowość, ponieważ będziesz musiał zadeklarować inną właściwość i będzie wymagać dodania
CodingKeys
wyliczenia (jeśli jeszcze go tam nie ma). Zaletą jest to, że nie musisz pisać niestandardowego kodu dekodowania / kodowania.Na przykład:
źródło
Możesz wdrożyć.
źródło
Jeśli nie chcesz implementować swoich metod kodowania i dekodowania, istnieje trochę brudne rozwiązanie dotyczące wartości domyślnych.
Możesz zadeklarować swoje nowe pole jako niejawnie rozpakowane opcjonalne i sprawdzić, czy jest zerowe po zdekodowaniu i ustawić wartość domyślną.
Przetestowałem to tylko z PropertyListEncoder, ale myślę, że JSONDecoder działa w ten sam sposób.
źródło
Natknąłem się na to pytanie, szukając dokładnie tego samego. Odpowiedzi, które znalazłem, nie były zbyt satysfakcjonujące, mimo że obawiałem się, że rozwiązania tutaj będą jedyną opcją.
W moim przypadku stworzenie niestandardowego dekodera wymagałoby mnóstwa gotowych rozwiązań, które byłyby trudne do utrzymania, więc szukałem innych odpowiedzi.
Natrafiłem na ten artykuł, który pokazuje interesujący sposób na przezwyciężenie tego w prostych przypadkach przy użyciu pliku
@propertyWrapper
. Najważniejsze dla mnie było to, że był on wielokrotnego użytku i wymagał minimalnej refaktoryzacji istniejącego kodu.W artykule przyjęto przypadek, w którym chciałbyś, aby brakująca właściwość logiczna miała domyślnie wartość false bez błędu, ale pokazuje również inne różne warianty. Możesz przeczytać to bardziej szczegółowo, ale pokażę, co zrobiłem dla mojego przypadku użycia.
W moim przypadku miałem
array
, że chciałem zostać zainicjowany jako pusty, jeśli brakowało klucza.Dlatego zadeklarowałem następujące
@propertyWrapper
i dodatkowe rozszerzenia:Zaletą tej metody jest to, że można łatwo rozwiązać problem w istniejącym kodzie, po prostu dodając
@propertyWrapper
do właściwości. W moim przypadku:Mam nadzieję, że pomoże to komuś, kto ma ten sam problem.
AKTUALIZACJA:
Po opublikowaniu tej odpowiedzi, kontynuując badanie sprawy, znalazłem ten inny artykuł, ale co najważniejsze, odpowiednią bibliotekę, która zawiera kilka popularnych, łatwych w użyciu
@propertyWrapper
plików do tego rodzaju przypadków:https://github.com/marksands/BetterCodable
źródło
Jeśli uważasz, że pisanie własnej wersji
init(from decoder: Decoder)
jest przytłaczające, radziłbym wdrożyć metodę, która sprawdzi wejście przed wysłaniem go do dekodera. W ten sposób będziesz mieć miejsce, w którym możesz sprawdzić brak pól i ustawić własne wartości domyślne.Na przykład:
Aby zainicjować obiekt z json, zamiast:
Init będzie wyglądał następująco:
W tej konkretnej sytuacji wolę mieć do czynienia z opcjami, ale jeśli masz inne zdanie, możesz sprawić, aby metoda customDecode (:) była rzucana
źródło