CodeGym /Cours /Swift SELF /JSON — objecte/tableau/primitifs

JSON — objecte/tableau/primitifs

Swift SELF
Niveau 58, Leçon 0
Disponible

1. Pourquoi avons-nous besoin de JSON

Si vous avez un jour enregistré une note dans une application, envoyé des données à un serveur ou simplement vu une réponse d'une API, vous avez déjà rencontré JSON — même si vous ne saviez pas que c'en était. On peut considérer JSON comme un format très populaire de « texte empaqueté », que les humains et les programmes lisent tous les deux plutôt bien. Il n'est pas le plus rapide, ni le plus compact, mais il est généralement compréhensible et portable.

Le problème commence quand le débutant pense : « Bon, ce n'est que du texte, on va parser ça d'une manière ou d'une autre. » Et puis, soudain, on découvre qu'une même clé peut être un nombre, une chaîne ou même être absente — et votre programme se transforme en détective : « où est donc le champ id, et pourquoi est-il aujourd'hui "7", et non 7 ? ». C'est précisément pour cela que nous parlerons non seulement de la « syntaxe JSON », mais aussi du schéma : un accord sur ce qui se trouve exactement à l'intérieur.

Pour relier cela à notre application d'apprentissage, imaginons que nous réalisons une mini-bibliothèque en console (le LibraryCLI fictif) : nous ajoutons des livres, nous les recherchons, et un jour nous voudrons les enregistrer. Pour le stockage, il nous faut un format — et JSON est justement l'une des options les plus typiques.

2. Structure de JSON : conteneurs et primitifs

JSON repose sur une idée très simple : il y a des conteneurs (types composés) qui peuvent contenir d'autres valeurs, et il y a des primitifs (valeurs atomiques). C'est comme des boîtes et des objets. Une boîte peut contenir des boîtes et des objets, tandis qu'un objet à l'intérieur de lui-même ne stocke plus rien.

JSON n'a que deux conteneurs : object et array. Et seulement quatre types de valeurs « simples » : string, number, bool, null. Si vous retenez cela, vous êtes déjà à mi-chemin d'une vie tranquille.

Pour illustrer, voici un petit tableau :

Catégorie Aspect en JSON Exemple
Object (objet)
{ ... }
{ "title": "Dune" }
Array (tableau)
[ ... ]
[1, 2, 3]
String (chaîne)
"..."
"Swift"
Number (nombre)
123, 3.14
2024
Bool (booléen)
true / false
false
Null
null
null

Important : JSON est un format textuel, mais sur le fond il décrit une structure de données. C'est précisément à ce point que naît la friction entre le JSON « faiblement typé » et le Swift « strictement typé » : JSON permet facilement « n'importe quoi », alors que Swift veut des types précis. Ce problème fondamental dans le monde de Swift est débattu depuis longtemps : quand les données sont « externes », elles ne sont pas obligées de correspondre à nos attentes.

3. Objet JSON {} : clé → valeur

Un objet JSON est un ensemble de paires « clé → valeur ». Dans la vie courante, il est pratique d'y voir un Dictionary, mais avec des règles strictes : les clés sont toujours des chaînes et sont toujours écrites entre guillemets doubles. Autrement dit, {"id": 1} — oui, {id: 1} — non (c'est déjà un « presque JSON », que JavaScript apprécie, mais que JSON n'approuve pas).

D'un point de vue pratique, un objet est adapté quand une entité possède des champs nommés : id, title, author. C'est exactement sous cette forme que nous décririons un livre dans notre application.

Exemple (simple chaîne, sans parsing) :

let bookJSON = """
{
  "id": 1,
  "title": "Dune",
  "isAvailable": true
}
"""

print(bookJSON.count) // par exemple, 55 (selon les espaces/retours à la ligne)

Faites attention aux détails : les clés sont entre guillemets, les valeurs ont des types différents, et JSON n'a aucun problème avec le fait que id soit un nombre, title une chaîne, et isAvailable un booléen. C'est normal et pratique.

Nuance importante de conception : si vous avez choisi la clé "isAvailable", cela fait partie du contrat du format. La renommer en "available" « juste parce que c'est plus joli » signifie casser la compatibilité de lecture des anciennes données.

4. Tableau JSON [] : liste de valeurs

Un tableau en JSON est une séquence de valeurs séparées par des virgules. Les valeurs peuvent être n'importe quoi : des primitifs, des objets, des tableaux. Si un objet est une « fiche avec des champs », un tableau est une « liste de fiches ».

Pour notre mini-bibliothèque, un tableau est une forme naturelle pour la liste des livres : [book1, book2, book3].

let tagsJSON = #"""
["swift", "json", "cli"]
"""#

print(tagsJSON) // ["swift", "json", "cli"]

Pourquoi utilise-t-on ici #""" ... """# ? C'est une chaîne brute : on peut y écrire des guillemets doubles sans les échapper. Nous ne sommes pas obligés de l'utiliser, mais parfois cela facilite la lecture visuelle.

Un autre exemple — un tableau d'objets :

let booksJSON = """
[
  { "id": 1, "title": "Dune" },
  { "id": 2, "title": "1984" }
]
"""

print(booksJSON.contains("\"title\"")) // true

L'idée pratique principale : si la racine du JSON est un tableau, alors au niveau supérieur vous n'avez pas de place pour des métadonnées. Cela deviendra important quand nous voudrons stocker la version du schéma, la date de mise à jour, la source des données, etc. (retenons simplement cette idée pour l'instant).

5. Primitifs JSON : string / number / bool / null

Les primitifs semblent simples jusqu'à ce qu'on commence à les stocker « sérieusement ». C'est précisément sur les primitifs que les attentes se brisent le plus souvent : "7" (chaîne) arrive soudain à la place de 7 (nombre), null apparaît là où vous attendiez une chaîne, et true se transforme en "true" (chaîne) — et voilà, c'est la galère.

En JSON, les primitifs sont les suivants :

  • string : uniquement entre guillemets doubles, avec échappement des caractères spéciaux (retour à la ligne, guillemets).
  • number : en JSON, il n'existe pas de Int et de Double distincts, c'est simplement un « nombre ». Dans l'implémentation, cela peut devenir un entier ou un flottant, mais dans le texte c'est une seule catégorie.
  • bool : uniquement true ou false en minuscules.
  • null : une valeur spéciale « vide ».
let primitivesJSON = """
{
  "title": "Dune",
  "year": 1965,
  "rating": 4.8,
  "isClassic": true,
  "subtitle": null
}
"""

print(primitivesJSON.contains("null")) // true

Du point de vue du schéma (de l'accord), il est important de décider : que signifie null ? Est-ce « valeur inconnue », « valeur intentionnellement vide », « valeur supprimée » ? JSON permet d'écrire null, mais le sens, c'est vous qui le définissez.

6. Imbrication : JSON comme arbre de données

Presque tout JSON réel est imbriqué. À l'intérieur d'un objet se trouve un tableau, à l'intérieur d'un tableau se trouvent des objets, à l'intérieur de ceux-ci, encore des tableaux… et ainsi de suite. Cela ressemble à un arbre, où chaque branche est soit un tableau, soit un objet, et les feuilles sont des primitifs.

Pour un livre, cela peut être : l'objet livre contient un tableau d'auteurs (des chaînes), un tableau de tags, ainsi qu'un objet imbriqué meta (par exemple, « ajouté aux favoris »).

let nestedJSON = """
{
  "id": 1,
  "title": "Dune",
  "authors": ["Frank Herbert"],
  "meta": { "addedBy": "user", "isFavorite": false }
}
"""

print(nestedJSON.contains("\"meta\"")) // true

Si cette structure vous semble encore difficile à « garder en tête », une idée simple aide : l'imbrication, ce n'est que des valeurs à l'intérieur d'autres valeurs. Et oui, c'est un état absolument normal pour JSON.

On peut même dessiner la « forme » des données :

flowchart TD
    A[Book JSON Object] --> B["id: number"]
    A --> C["title: string"]
    A --> D["authors: array"]
    D --> E["author: string"]
    A --> F["meta: object"]
    F --> G["addedBy: string"]
    F --> H["isFavorite: bool"]

7. Schéma et conception du stockage pour LibraryCLI

Qu'est-ce qu'un schéma et pourquoi c'est pénible sans lui

Ici commence la vie d'adulte. Une chaîne JSON, à elle seule, ne vous garantit presque rien, sinon que c'est un JSON valide. Mais pour un programme, autre chose est plus important : la structure des données correspond-elle à ce que nous attendons ?

Un schéma (dans notre sens pédagogique) est un accord sur quatre choses :

1) quel est le type de la racine (objet {} ou tableau []),
2) quelles sont les clés des objets et comment s'appellent-elles,
3) quels sont les types des valeurs (chaîne/nombre/tableau/objet…),
4) quels champs sont obligatoires et lesquels peuvent être absents.

Pourquoi est-ce important ? Parce que Swift est strict : si vous avez écrit qu'un livre possède un id: Int, vous vous êtes intérieurement engagé à ce que les données permettent toujours d'obtenir un entier. Or JSON ne vous a promis rien de tel. C'est précisément cet écart qui est une source majeure d'erreurs et de casse-tête lorsqu'on travaille avec des données réelles.

Petite analogie : un JSON sans schéma, c'est « un colis sans bordereau de contenu ». En apparence, la boîte est arrivée, mais ce qu'il y a dedans — vous ne le saurez qu'en l'ouvrant. Et quand vous l'avez ouverte, il peut s'avérer que ce n'est pas un livre, mais une tasse en céramique, et vous avez déjà secoué la boîte pendant le trajet (dans le code, cela s'appelle fatalError, mais aujourd'hui nous sommes gentils).

« Pas de valeur » : clé absente vs clé présente mais null

En pratique, c'est l'un des points les plus subtils, qui casse souvent la logique — même chez des personnes qui « savent déjà JSON ». En JSON, il existe deux scénarios différents de « vide », et ils peuvent signifier des choses différentes :

1) la clé est totalement absente
2) la clé existe, mais la valeur est null

Comparez :

let missingKeyJSON = """
{ "id": 1, "title": "Dune" }
"""

let nullKeyJSON = """
{ "id": 1, "title": "Dune", "subtitle": null }
"""

print(missingKeyJSON.contains("subtitle")) // false
print(nullKeyJSON.contains("subtitle"))    // true

Et voilà la question du schéma : que signifie, pour votre application, l'absence de subtitle ? Et que signifie subtitle: null ? Est-ce la même chose ou des états différents ?

Dans le format de stockage de notre bibliothèque, il est généralement pratique de considérer que subtitle peut être optionnel : parfois il n'existe pas — et tout va bien. Mais même dans ce cas, mieux vaut choisir un seul style : soit le champ peut être absent, soit il est toujours présent, mais parfois null. Les deux approches sont viables — l'important est de ne pas les mélanger par hasard.

À quoi ressemblera le fichier entier : racine et métadonnées

Voici un moment très pratique : nous n'écrivons pas encore de modèles Codable et nous ne codons/décodons pas encore, mais nous décidons déjà à quoi ressemblera le fichier. Ce choix, c'est comme choisir l'agencement d'un appartement : on peut ensuite déplacer une chaise, mais démolir un mur porteur est plus difficile.

Imaginons que nous voulions stocker une liste de livres. Qu'un livre ait les champs suivants (comme idée) : id, title, éventuellement subtitle, un tableau authors, un tableau tags.

À quoi pourrait ressembler un seul Book en JSON ? Exemple :

let bookV1JSON = """
{
  "id": 1,
  "title": "Dune",
  "subtitle": null,
  "authors": ["Frank Herbert"],
  "tags": ["sci-fi", "classic"]
}
"""

print(bookV1JSON.contains("\"authors\"")) // true

Maintenant, le choix clé du schéma : quelle sera la racine du fichier ?

Si la racine est un tableau de livres, c'est simple et compact :

[
  { ...book1... },
  { ...book2... }
]

Mais dès que vous voudrez stocker des métadonnées (par exemple, la version du schéma), le tableau vous limitera : il n'y a nulle part où mettre les métadonnées. C'est pourquoi, en pratique d'ingénierie, on fait souvent de la racine un objet-conteneur, avec des méta-champs et un tableau de données.

Un champ très typique est "schemaVersion" : on le rencontre dans des formats JSON réels comme mécanisme de « versionnement du schéma ». Par exemple, dans les formats autour de SwiftPM, le champ "schemaVersion" est aussi utilisé dans les manifestes JSON pour distinguer les versions de la structure du fichier.

Nous ne fixons pas encore ici le format final (ce sera une autre leçon du jour), mais l'idée importante est déjà là : le schéma, ce n'est pas seulement “à quoi ressemble Book”, c'est aussi “à quoi ressemble le fichier dans son ensemble”.

8. Vérifications rapides et validité du JSON

Mini-vérification de la forme du JSON « à l'œil »

Il est parfois utile de pouvoir regarder rapidement un JSON comme un texte et comprendre « la racine est un objet ou un tableau », sans faire un vrai parsing. Ce n'est pas un remplacement du décodage normal, mais comme diagnostic, cela fonctionne.

func jsonRootKind(_ text: String) -> String {
    let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
    if trimmed.hasPrefix("{") { return "object" }
    if trimmed.hasPrefix("[") { return "array" }
    return "unknown"
}

print(jsonRootKind("{\"id\":1}")) // object
print(jsonRootKind("[1,2,3]"))    // array

C'est volontairement une vérification « bête », mais cela aide le débutant à ne plus se mélanger : {} et [] sont deux « conteneurs » différents, et cela détermine la manière dont vous interpréterez les données.

JSON valide vs « presque JSON »

JSON est strict et pardonne souvent peu les « presque correct ». C'est particulièrement douloureux pour ceux qui sont habitués à des langages/formats où les commentaires, les guillemets simples et les « virgules de chance » sont permis.

Voici un « presque JSON » typique, qui a l'air plausible, mais qui est un JSON invalide :

Exemple (en tant que chaîne — elle se compile, mais ce n'est pas un JSON valide) :

let almostJSON = """
{
  'id': 1, // guillemets simples et commentaires
  "title": "Dune",
}
"""

print(almostJSON) // ressemble à du JSON, mais ce n'est pas du JSON

Ici, il y a immédiatement trois problèmes : les guillemets simples, les commentaires, la virgule après le dernier élément. Et ce n'est pas du pinaillage : si vous stockez les données dans un fichier, le moindre détail de ce genre transforme la lecture en erreur.

9. Erreurs typiques

Erreur n°1 : confondre JSON et « objet JavaScript ».
Histoire très fréquente : quelqu'un écrit { id: 1 } et pense sincèrement que c'est du JSON. Cela ressemble à du JSON, mais JSON exige que les clés soient des chaînes entre guillemets doubles : { "id": 1 }. Si vous gardez en tête la règle « en JSON, une clé est toujours une chaîne », vous cessez de marcher sur ces râteaux.

Erreur n°2 : ne pas se mettre d'accord sur la racine et changer d'avis sans cesse.
Aujourd'hui vous avez enregistré le fichier comme un tableau de livres, demain vous décidez qu'il faut un objet avec des métadonnées, et le lendemain vous ajoutez encore un autre tableau à côté. Résultat : lire les anciennes données devient difficile, les nouvelles encore plus. La racine du fichier fait partie du schéma : une fois choisie, on s'y tient.

Erreur n°3 : mélanger les types « selon l'humeur » : parfois id est un nombre, parfois une chaîne.
JSON permet de stocker "id": "7" et "id": 7, mais pour l'application ce sont deux réalités différentes. Si vous voulez un identifiant numérique, stockez-le comme un nombre. Si vous le voulez sous forme de chaîne, stockez-le comme une chaîne. Le mélange conduit presque toujours au fait que vous passez la moitié du temps à « réparer les données » au lieu de faire évoluer le programme.

Erreur n°4 : confondre l'absence de clé et null.
À un endroit vous n'avez pas écrit subtitle, à un autre vous avez écrit subtitle: null, à un troisième vous avez écrit subtitle: "". Puis vous essayez de comprendre pourquoi, dans l'interface, parfois on affiche « (pas de sous-titre) », parfois une chaîne vide, et parfois la logique s'effondre complètement. Ces cas peuvent être des états différents — mais alors cela doit faire partie du schéma. Si vous n'avez pas besoin d'une telle finesse, choisissez une seule option et tenez-vous-y.

Erreur n°5 : changer les noms des clés « pour faire joli ».
Aujourd'hui vous avez "schemaVersion", demain vous décidez que "schema" est plus court, et après-demain que "version" serait mieux. De tels changements cassent la compatibilité et transforment une lecture JSON simple en migrations et en rustines. Dans les outils réels qui stockent des fichiers JSON, les clés et "schemaVersion" sont généralement figées comme partie du contrat du format.

1
Mission
Swift SELF, niveau 58, leçon 0
Bloqué
Profil dans le journal
Profil dans le journal
1
Mission
Swift SELF, niveau 58, leçon 0
Bloqué
Détecteur de conteneur
Détecteur de conteneur
1
Mission
Swift SELF, niveau 58, leçon 0
Bloqué
Brouillon de caisse
Brouillon de caisse
1
Mission
Swift SELF, niveau 58, leçon 0
Bloqué
Fichier de la bibliothèque
Fichier de la bibliothèque
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION