Mon premier JSON Schema

Le format JSON (JavaScript Object Notation) est un des plus utilisés sur le web. Il est léger et permet d’échanger des données. Je propose la rédaction d’un JSON Schema pour formaliser les contenus, à partir d’un l’exemple concret de données que j’utilise déjà.

Un JSON tout simple

Pour un site patrimonial qui liste les églises protestantes ouvertes en Suisse romande, j’ai utilisé dès le début des données formatées en JSON. Chaque bâtiment a son propre fichier, assez lisible, qui ressemble à ceci:

{
    "title": "Temple de Colombier",
    "site": "https://www.eren.ch/barc/batiments/temple-de-colombier/",
    "maps": "https://goo.gl/maps/3KSuvNTqXsdSFmnc9",
    "rue": "Rue du Château 3a",
    "npa": 2013,
    "localite": "Colombier",
    "commune": "Milvignes",
    "canton": "Neuchâtel",
    "latitude": 46.9666066,
    "longitude": 6.8623137,
    "ouverture": "7/7",
    "horaire": "8:00-20:00",
    "pbc": "B",
    "vitraux": ["Pierre-Eugène Bouvier"]
}

J’ai ajouté champs de données au fur et à mesure de la création du site, ne sachant pas quelles informations j’allais découvrir dans mes recherches.

Un JSON plus structuré

S’il fallait modifier quelque chose, je structurerais peut-être les données un peu plus fortement, par exemple ainsi:

{
    "title": "Temple de Colombier",
    "liens": {
        "site": "https://www.eren.ch/barc/batiments/temple-de-colombier/",
        "maps": "https://goo.gl/maps/3KSuvNTqXsdSFmnc9"
    },
    "adresse": {
        "rue": "Rue du Château 3a",
        "npa": 2013,
        "localite": "Colombier",
        "commune": "Milvignes",
        "canton": "Neuchâtel"   
    },
    "coordonnes": {
        "latitude": 46.9666066,
        "longitude": 6.8623137
    },
    "ouverture": {
        "jours": "7/7",
        "horaire": "8:00-20:00"
    },
    "patrimoine": {
        "pbc": "B",
        "vitraux": ["Pierre-Eugène Bouvier"]
    }
}

C’ess très théorique, parce qu’en voyant le résultat, je ne suis pas certain que ça apporterait beaucoup. Le «JSON tout simple» est donc celui que je conserve pour produire le site et sur lequel portent les exemples qui suivant.

Objectifs du schema JSON

Si le format JSON est strict du point de vue le la forme, il laisse toute liberté pour les contenus. Un JSON Schema permet de fixer des règles pour la création d’un contenu JSON.

Le schema que je vais créer doit notamment servir à vérifier:

Ainsi je pourrai vérifier la validité de mon corpus d’édifices en une seule commande, par exemple avec JSONSchema. Ou tester un contenu directement en ligne avec JSON Schema Validator.

Création d’un schema JSON

L’utilisation de JSON est complètement libre, mais un JSON Schema permet de fixer des règles et de valider des contenus informatiquement. Pour ma culture générale, j’ai créé un schema pour Églises ouvertes en Suisse romande.

Je le copie intégralement, même si c’est un peu long. Puis je commente certaines parties dans la suite du billet. La version courante est disponible sur le le site eglises-ouvertes.ch.

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://eglises-ouvertes.ch/schema.json",
    "title": "église ouverte",
    "description": "description complète d’une des Églises ouvertes de Suisse romande",
    "type": "object",
    "properties": {
        "title": {
            "description": "nom du bâtiment",
            "type": "string"
        },
        "nomCourt": {
            "description": "version simplifiée du nom du bâtiment",
            "type": "string"
        },
        "site": {
            "description": "page de présentation du bâtiment (horaire d’ouverture impératif)",
            "type": "string"
        },
        "maps": {
            "description": "fiche Google Mas du bâtiment (horaire d’ouverture impératif)",
            "type": "string"
        },
        "rue": {
            "description": "rue avec numéro",
            "type": "string"
        },
        "localite": {
            "description": "village dans une plus grande commune",
            "type": "string"
        },
        "commune": {
            "type": "string"
        },
        "npa": {
            "description": "numéro postal d’acheminement",
            "type": "integer",
            "minimum": 1000,
            "maximum": 9999
        },
        "canton": {
            "description": "un canton de Suisse romande",
            "enum": ["Berne", "Fribourg", "Genève", "Jura", "Neuchâtel", "Valais", "Vaud" ]
        },
        "latitude": {
            "type": "number",
            "minimum": 45,
            "maximum": 48
        },
        "longitude": {
            "type": "number",
            "minimum": 5,
            "maximum": 11
        },
        "ouverture": {
            "description": "jours d’ouverture (souvent 7/7)",
            "type": "string"
        },
        "horaire": {
            "description": "heures d’ouverture (parfois 24/24)",
            "type": "string"
        },
        "pbc": {
            "description": "patrimoine des biens culturels suisses",
            "enum": [ "A", "B" ]
        },
        "vitraux": {
            "description": "nom d’artiste",
            "type": "array",
            "items": { "type": "string" }
        }
    },
    "anyOf": [
        { "required": [ "site" ]},
        { "required": [ "maps" ]}
    ],
    "required": [
        "title",
        "rue",
        "npa",
        "commune",
        "canton",
        "latitude",
        "longitude",
        "ouverture",
        "horaire"
    ],
    "additionalProperties": false
}

Validation des champs

En début de fichier, ce sont des champs qui demandent des chaînes de caractères (du texte libre). Cela n’appelle pas de commentaires particuliers.

Pour le numéro postal d’acheminement, j’impose un nombre de 4 chiffres entre 1000 et 9999. On pourrait limiter aux NPA de Suisse romande, mais c’est très bien ainsi:

"npa": {
    "description": "numéro postal d’acheminement",
    "type": "integer",
    "minimum": 1000,
    "maximum": 9999
}

Pour le canton, on limite le choix aux cantons romands (ou avec une partie romande) et il ne peut y en avoir qu’un:

"canton": {
    "description": "un canton de Suisse romande",
    "enum": ["Berne", "Fribourg", "Genève", "Jura", "Neuchâtel", "Valais", "Vaud" ]
},

La latitude et la longitude sont limitées dans des valeurs proches de celles de la Suisse:

"latitude": {
    "type": "number",
    "minimum": 45,
    "maximum": 48
},
"longitude": {
    "type": "number",
    "minimum": 5,
    "maximum": 11
}

Si elle est précisée, l’inscription à l’Inventaire des biens culturels d’importance nationale et régionale (PBC) peut prendre la valeur A ou la valeur B:

"pbc": {
    "description": "patrimoine des biens culturels suisses",
    "enum": [ "A", "B" ]
}

Les artistes ayant créé des vitraux peuvent être plusieurs pour un seul édifice:

"vitraux": {
    "description": "nom d’artiste",
    "type": "array",
    "items": { "type": "string" }
}

Conditions de validation

Au delà de la validation champ par champ, des conditions plus globales sont déclarées.

En premier lieux, certaines données sont nécessaires et il faut s’assurer de leur existence:

"required": [
    "title",
    "rue",
    "npa",
    "commune",
    "canton",
    "latitude",
    "longitude",
    "ouverture",
    "horaire"
]

Beaucoup plus intéressant, l’obligation de la présence d’un site cible ou d’une fiche Google Maps (ou les deux):

"anyOf": [
    { "required": [ "site" ]},
    { "required": [ "maps" ]}
]

Finalement, l’interdiction d’ajouter des champs autres que ceux déclarés dans le schéma:

"additionalProperties": false

Assurances formelles et validité des contenus

C’était mon premier essai de rédaction d’un schema JSON. J’ai trouvé l’exercice plutôt intéressant. C’est toujours amusant de lire une référence technique sur un écran et d’écrire le fichier de code sur l’autre.

Il faudrait aller un peu plus loin pour certaines données, par exemple avec des expressions régulières pour valider des plages horaires. Mais je m’arrête là pour le moment.

L’enjeu bien réel derrière cet exercice, c’est que la validité formelle ne dit rien de la validité réelle des contenus. Elle assure la présence de contenus, non leur justesse. Il y a quelque chose de paradoxal à formaliser des données alors que les informations n’existent souvent pas en ligne.

Le rachitisme de Églises ouvertes en Suisse romande est une conséquence directe de la paresse de celles et ceux qui pourraient publier des contenus utiles. Mais pas une question de format de données.