Fiche de triche codable
Codable
était l’une des fonctionnalités phares de Swift 4.0, apportant avec elle une conversion incroyablement fluide entre les types de données Swift et JSON. Il s’est ensuite amélioré dans Swift 4.1 grâce à l’ajout de nouvelles fonctionnalités, et je m’attends à des choses encore plus grandes à l’avenir.
Dans cet article, je veux fournir des échantillons de code rapides pour vous aider à répondre aux questions courantes et à résoudre les problèmes courants, tout en utilisant Codable
.
SPONSORED Construire et maintenir une infrastructure d’abonnement in-app est difficile. Heureusement, il existe une meilleure solution. Avec RevenueCat, vous pouvez mettre en place des abonnements pour votre application en quelques heures, et non en plusieurs mois, afin de pouvoir retourner à la création de votre application.
Essayez-le gratuitement
Sponsorisez Hacking with Swift et touchez la plus grande communauté Swift au monde !
Encodage et décodage de JSON
Commençons par les bases : convertir un peu de JSON en structs Swift.
D’abord, voici un peu de JSON avec lequel travailler :
let json = """"""let data = Data(json.utf8)
La dernière ligne le convertit en un objet Data
car c’est avec cela que les décodeurs Codable
fonctionnent.
Puis nous devons définir une struct Swift qui contiendra nos données finies:
struct User: Codable { var name: String var age: Int}
Maintenant nous pouvons aller de l’avant et effectuer le décodage:
let decoder = JSONDecoder()do { let decoded = try decoder.decode(.self, from: data) print(decoded.name)} catch { print("Failed to decode JSON")}
Cela imprimera « Paul », qui est le nom du premier utilisateur dans le JSON.
Conversion de cas
Un problème commun avec JSON est qu’il utilisera un formatage différent pour ses noms de clés que nous voulons utiliser dans Swift. Par exemple, vous pourriez obtenir « prénom » dans votre JSON et avoir besoin de le convertir en une propriété firstName
.
Maintenant, une solution évidente ici est juste de changer soit le JSON ou vos types Swift afin qu’ils utilisent la même convention de dénomination, mais nous n’allons pas faire cela ici. Au lieu de cela, je vais supposer que vous avez un code comme celui-ci :
let json = """"""let data = Data(json.utf8)struct User: Codable { var firstName: String var lastName: String}
Pour que cela fonctionne, nous devons changer une seule propriété dans notre décodeur JSON :
let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCase
Ceci indique à Swift de faire correspondre les noms en casse serpent (names_written_like_this) aux noms en casse camel (namesWrittenLikeThis).
Mappage de différents noms de clés
Si vous avez des clés JSON qui sont complètement différentes de vos propriétés Swift, vous pouvez les mapper en utilisant une CodingKeys
enum.
Regardez ce JSON :
let json = """"""
Ces noms de clés ne sont pas géniaux, et vraiment nous aimerions convertir ces données dans une structure comme celle-ci :
struct User: Codable { var firstName: String var lastName: String var age: Int}
Pour que cela se produise, nous devons déclarer une CodingKeys
enum : un mappage que Codable
peut utiliser pour convertir les noms JSON en propriétés pour notre structure. Il s’agit d’une enum régulière qui utilise des chaînes de caractères pour ses valeurs brutes afin que nous puissions spécifier à la fois notre nom de propriété (le cas de l’enum) et le nom JSON (la valeur de l’enum) en même temps. Il doit également se conformer au protocole CodingKey
, ce qui fait que cela fonctionne avec le protocole Codable
.
Donc, ajoutez cet enum à la struct:
enum CodingKeys: String, CodingKey { case firstName = "user_first_name" case lastName = "user_last_name" case age}
Ceci sera maintenant capable de décoder le JSON comme prévu.
Note : L’enum s’appelle CodingKeys
et le protocole s’appelle CodingKey
.
Travailler avec les dates ISO-8601
Il existe de nombreuses façons de travailler avec les dates sur internet, mais l’ISO-8601 est la plus courante. Elle encode la date complète au format AAAA-MM-JJ, puis la lettre « T » pour signaler le début des informations temporelles, puis l’heure au format HH:MM:SS, et enfin un fuseau horaire. Le fuseau horaire « Z », abréviation de « Zulu time » est couramment utilisé pour signifier UTC.
Codable
est capable de gérer ISO-8601 avec un convertisseur de date intégré. Ainsi, étant donné ce JSON :
let json = """"""
Nous pouvons le décoder comme ceci :
struct Baby: Codable { var firstName: String var timeOfBirth: Date}let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .iso8601
Cela permet l’analyse de la date ISO-8601, qui convertit de 1999-04-03T17:30:31Z en une instance Date
, tout en gérant la conversion de la casse serpent en casse chameau.
Travailler avec d’autres dates courantes
Swift est livré avec un support intégré pour trois autres formats de date importants. Vous les utilisez comme vous utilisez les dates ISO-8601 comme indiqué ci-dessus, donc je vais juste en parler brièvement :
- Le format
.deferredToDate
est le format de date propre à Apple, et il suit le nombre de secondes et de millisecondes depuis le 1er janvier 2001. Ce n’est pas vraiment utile en dehors des plateformes d’Apple. - Le format
.millisecondsSince1970
suit le nombre de secondes et de millisecondes depuis le 1er janvier 1970. C’est assez courant en ligne. - Le format
.secondsSince1970
retrace le nombre de secondes entières depuis le 1er janvier 1970. C’est extrêmement commun en ligne, et est deuxième après l’ISO-8601.
Travailler avec des dates personnalisées
Si votre format de date ne correspond pas à l’une des options intégrées, ne désespérez pas : Codable peut analyser des dates personnalisées en se basant sur un formateur de date que vous créez.
Par exemple, ce JSON suit le jour où un étudiant a obtenu son diplôme universitaire :
let json = """"""
Cela utilise le format de date DD-MM-YYYY, qui ne fait pas partie des options intégrées de Swift. Heureusement, vous pouvez fournir une instance DateFormatter
préconfigurée comme stratégie de décodage de date, comme ceci:
let formatter = DateFormatter()formatter.dateFormat = "dd-MM-yyyy"let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .formatted(formatter)
Travailler avec des dates bizarres
Parfois, vous obtiendrez des dates si étranges que même DateFormatter
ne peut pas les gérer. Par exemple, vous pourriez obtenir du JSON qui stocke les dates en utilisant le nombre de jours qui se sont écoulés depuis le 1er janvier 1970 :
let json = """"""
Pour que cela fonctionne, nous devons écrire un décodeur personnalisé pour la date. Tout le reste sera toujours géré par Codable
– nous fournissons juste une fermeture personnalisée qui traitera la partie dates.
Vous pourriez essayer de faire des mathématiques pirates ici, comme multiplier le nombre de jours par 86400 (le nombre de secondes dans un jour), puis utiliser la méthode addTimeInterval()
de Date
. Cependant, cela ne prendra pas en compte l’heure d’été et d’autres problèmes de date, donc une meilleure solution est d’utiliser DateComponents
et Calendar
comme ceci:
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()}
Avertissement : Si vous devez analyser beaucoup de dates, n’oubliez pas que cette fermeture sera exécutée pour chacune d’entre elles – faites vite !
Exploitation de données hiérarchiques de manière simple
Tout JSON non trivial est susceptible d’avoir des données hiérarchiques – une collection de données imbriquées dans une autre. Par exemple:
let json = """"""
Codable
est capable de gérer cela très bien, tant que vous pouvez décrire les relations clairement.
Je trouve que la façon la plus simple de le faire est d’utiliser des structs imbriqués, comme ceci:
struct User: Codable { struct Name: Codable { var firstName: String var lastName: String } var name: Name var age: Int}
L’inconvénient est que si vous voulez lire le prénom d’un utilisateur, vous devez utiliser user.name.firstName
, mais au moins le travail d’analyse syntaxique réel est trivial – notre code existant fonctionne déjà !
Parser des données hiérarchiques à la dure
Si vous voulez parser des données hiérarchiques dans une structure plate – c’est-à-dire, vous voulez être en mesure d’écrire user.firstName
plutôt que user.name.firstName
– alors vous devez faire un peu de parsing vous-même. Ce n’est pas trop difficile, cependant, et Codable
le rend magnifiquement sûr de type.
Premièrement, créez la structure que vous voulez finir avec:
struct User: Codable { var firstName: String var lastName: String var age: Int}
Deuxièmement, nous devons définir les clés de codage qui décrivent où les données peuvent être trouvées dans la hiérarchie.
Regardons à nouveau le JSON:
let json = """"""
Comme vous pouvez le voir, à la racine, il y a une clé appelée « nom » et une autre appelée « âge », donc nous devons ajouter cela comme nos clés de codage racine. Mettez ceci à l’intérieur de votre struct:
enum CodingKeys: String, CodingKey { case name, age}
À l’intérieur de « name » se trouvaient deux autres clés, « first_name » et « last_name », donc nous allons créer des clés de codage pour ces deux-là. Ajoutez ceci :
enum NameCodingKeys: String, CodingKey { case firstName, lastName}
Maintenant pour la partie difficile : nous devons écrire un initialisateur personnalisé et une méthode de codage personnalisée pour notre type. Commencez par ajouter cette méthode vide à votre structure:
init(from decoder: Decoder) throws {}
À l’intérieur, la première chose que nous devons faire est de tenter de tirer un conteneur que nous pouvons lire en utilisant les clés de notre enum CodingKeys
, comme ceci:
let container = try decoder.container(keyedBy: CodingKeys.self)
Une fois que c’est fait, nous pouvons tenter de lire notre propriété age
. Cela est fait d’une manière sûre : vous lui dites le type que vous voulez décoder (Int.self
pour notre âge), ainsi qu’un nom de clé de l’énumération CodingKeys
:
age = try container.decode(Int.self, forKey: .age)
Puis nous devons creuser un niveau plus bas pour lire nos données de nom. Comme vous l’avez vu précédemment, « name » est une clé de haut niveau dans notre enum CodingKeys
, mais c’est en fait un conteneur imbriqué d’autres valeurs que nous devons lire à l’intérieur. Donc, nous devons extraire ce conteneur :
let name = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)
Et finalement, nous pouvons lire deux chaînes pour les clés .firstName
et .lastName
:
firstName = try name.decode(String.self, forKey: .firstName)lastName = try name.decode(String.self, forKey: .lastName)
Cela termine l’initialisateur personnalisé, mais nous avons encore une méthode à écrire : encode(to:)
. C’est effectivement l’inverse de l’initialisateur que nous venons d’écrire, parce que son travail consiste à reconvertir nos propriétés en conteneur imbriqué comme il convient.
Cela signifie créer un conteneur basé sur notre enum CodingKeys
et y écrire age
, puis créer un conteneur imbriqué basé sur notre enum NameCodingKeys
, et y écrire à la fois firstName
et 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)}
Cela termine tout notre code. Avec cela en place, vous pouvez maintenant lire la propriété firstName
directement, ce qui est beaucoup plus agréable!
SPONSORED Construire et maintenir une infrastructure d’abonnement in-app est difficile. Heureusement, il existe une meilleure solution. Avec RevenueCat, vous pouvez mettre en place des abonnements pour votre application en quelques heures, et non en plusieurs mois, afin de pouvoir retourner à la création de votre application.
Essayez-le gratuitement
Sponsorisez Hacking with Swift et touchez la plus grande communauté Swift au monde !
.
Leave a Reply