Composant intermediaire de normalisation et nettoyage des données. Se place entre <dsfr-data-source> et <dsfr-data-query> pour nettoyer les données avant traitement.

Invisible : Ce composant ne rend rien visuellement. Il normalise les données et les redistribue aux composants en aval via le systeme d'événements.

Position dans le pipeline

dsfr-data-source → dsfr-data-normalize → dsfr-data-query → dsfr-data-chart

Normaliser avant dsfr-data-query permet aux filtres et agrégations de travailler sur des données propres (evite les comparaisons string vs number).

Cas d'usage

Attributs

AttributTypeDéfautDescription
idString-Requis. Identifiant de la sortie.
sourceString""ID de la source de données a ecouter
numericString""Champs a convertir en nombre (virgule-séparés). Ex: "population, surface"
numeric-autoBooleanfalseDetection automatique des champs numériques via looksLikeNumber()
renameString""Renommage de clés. Format : "ancien:nouveau | ancien2:nouveau2". Un : ou | littéral dans un nom s'échappe en percent (%3A, %7C), comme dans where.
trimBooleanfalseSupprime les espaces en debut/fin de toutes les clés et valeurs string
strip-htmlBooleanfalseSupprime les balises HTML des valeurs string
replaceString""Remplacement de valeurs, sur tous les champs. Format : "pattern:remplacement | pattern2:remplacement2". Le pattern est comparé à la valeur entière (égalité stricte, pas de regex) ; un remplacement vide supprime la valeur. Un :, |, , ou % littéral dans le pattern ou le remplacement s'échappe en percent (%3A, %7C, %2C, %25), comme dans where (#676) : replace="10%3A00:10h" récrit « 10:00 » en « 10h ». La comparaison porte sur la forme chaîne de la valeur : une colonne numérique est concernée aussi (#730). Un champ TABLEAU (multivalué venu de la source) est remplacé élément par élément, longueur conservée et sans dédoublonnage (#774). Limite : le remplacement s'exécute AVANT split, il ne voit donc pas les tableaux fabriqués par split — il agit sur la chaîne entière avant la découpe. Pour un recodage plus riche (sous-chaîne, année d'une date ISO), utiliser compute avec replace() ou year().
replace-fieldsString""Remplacement ciblé par champ. Format : "CHAMP:pattern:remplacement | CHAMP2:p:r". Les deux premiers : sont des délimiteurs, le remplacement peut contenir des : bruts. Un : littéral dans le nom du champ ou dans le pattern s'échappe en %3A (%7C, %2C et %25 sont aussi décodés), comme dans where (#676) : replace-fields="h:10%3A00:10h". La comparaison porte sur la forme chaîne de la valeur : une colonne numérique est concernée aussi, replace-fields="annee:2024:2024-2025" fonctionne (#730). Sur un champ tableau venu de la source, élément par élément ; pas sur un tableau fabriqué par split, découpé après (#774, même limite que replace). Pas de regex : pour un recodage plus riche, voir compute (replace(), year()).
flattenString""Clé du sous-objet a aplatir au premier niveau. Supporte la dot notation (ex: "data.attributes").
splitString""Découpe des champs multivalués (chaîne avec séparateur) en vrais tableaux, comme une ChoiceList Grist. Format : "champ:sep, champ2:sep2" ; séparateur par défaut : la virgule ("champ" seul). Ex : "Axes:|, Cibles:;". Chaque élément est trimé, les éléments vides sont écartés, une chaîne vide donne un tableau vide. Les valeurs non-string (tableau déjà forme, null, nombre) sont laissées telles quelles. Les composants aval traitent ces tableaux comme des champs multi-valeurs (facettes : une valeur par élément).
roundString""Arrondit les champs numériques à l'entier (ou à N décimales). Format: "champ1, champ2" ou "champ1:2, champ2:0"
lowercase-keysBooleanfalseMet toutes les clés en minuscules
computeString""Colonnes calculées, ligne à ligne, en dernier (sur les valeurs déjà typées par numeric / round / rename). Format : cible = expression; cible2 = expression2 (une assignation suivante peut relire une colonne calculée avant elle). Grammaire (ADR-105, #671) : - arithmétique + - * /, parenthèses, moins unaire ; + concatène dès qu'un côté n'est pas numérique ; littéraux texte 'entre quotes simples', nombres à point ; - littéraux null, true, false ; - fonctions en liste blanche, appel f(a, b) : dates year(d), month(d), day(d) (ISO ou Date, sinon null) ; nombres round(x, n), abs(x), floor(x), ceil(x) (non numérique → null) ; texte lower(s), upper(s), trim(s), len(s), concat(a, b, …), replace(s, 'de', 'vers') (littéral, toutes les occurrences, pas de regex) ; absence coalesce(a, b, …) (première valeur non nulle), is_null(x), is_empty(x) (null, '' ou tableau vide) ; tableaux join(arr, ', '), contains(arr_ou_texte, v) ; - conditions when COND then EXPR [when … then …]… else EXPR — le else est obligatoire (erreur de configuration sinon) ; comparaisons d'égalité = et != et d'ordre (inférieur, inférieur ou égal, supérieur, supérieur ou égal, avec les signes usuels — grammaire complète dans le guide « Colonnes calculées » de la skill), and, or, not. L'égalité est lâche comme celle de where (nombre ↔ chaîne numérique) : when cat = 'A' et where="cat:eq:A" gardent les mêmes lignes ; une cellule vide n'égale jamais un nombre (montant = 0 ne classe pas les montants non renseignés en zéro). Les comparaisons d'ordre se font en nombre quand les deux côtés sont numériques, en texte sinon (dates ISO comprises) ; null, undefined et '' ne matchent jamais. CHAMP TABLEAU (#953, ex-#842) : = regarde DANS le tableau — when tags = 'urgent' est VRAI pour ['urgent','social'], comme contains(tags, 'urgent'), et comme le portail sur une clause déléguée. != en est la négation, donc faux sur cette même ligne. contains() garde son utilité sur du TEXTE, où il cherche une sous-chaîne ; sur un tableau, les deux se rejoignent. Dériver ici un booléen puis filtrer where="a_urgent:eq:1" en aval reste valide — et utile, le filtre portant alors sur un scalaire regroupable et délégable — mais n'est plus NÉCESSAIRE pour filtrer un champ tableau : where="tags:eq:urgent" le fait directement. - arithmétique - * / et moins unaire : un opérande absent ou non numérique rend null (jamais un 0 plausible), une division par zéro rend null (jamais Infinity). Exemples : solde = actif - passif (null si l'un des deux manque), tranche = when montant = 0 then 'Nul' when is_empty(montant) then 'Inconnu' else 'Renseigné', type = coalesce(type_entreprise, 'Non renseigné'), annee = year(date_notification), pct = round(part * 100, 1), serie = Indicateurs + ' / ' + Sous_theme ; une tranche par seuils s'écrit avec les opérateurs d'ordre (voir le guide). Fonction hors liste, arité fausse, when sans else, expression trop longue ou trop imbriquée : erreur de configuration (console + data-dsfr-config-error), état d'erreur en aval — jamais une colonne silencieusement vide. Aucun eval : tokenizer, parseur, AST ; seuls les champs de la ligne sont accessibles. Hors périmètre : valeurs agrégées (query / kpi), ligne précédente, cumul.
foldString""Repli de colonnes booléennes parallèles en un champ multi-valeurs (#677) — le motif open data « une colonne Oui/Non par modalité » (handicap_moteur, handicap_visuel…). Format : "motif:cible, motif2:cible2". Le joker * n'est accepté qu'en début ou en fin de motif (handicap_*, *_ok) ; un motif sans joker désigne une colonne exacte ; plusieurs motifs peuvent viser la même cible. Chaque ligne reçoit dans cible le tableau des colonnes dont la valeur est vraie au sens de toBoolean (Oui/Non, 1/0, true/false, X/vide…), étiquetées par la partie variable du motif (handicap_moteur donne « moteur ») ou par le nom complet de la colonne pour un motif sans joker. Les colonnes sources sont conservées (voir fold-drop). S'exécute après rename et lowercase-keys, avant compute : les motifs se lisent sur les noms renommés, qui servent donc d'étiquettes (rename="handicap_moteur:handicap_Moteur" donne « Moteur »). Le tableau obtenu se filtre avec dsfr-data-facets comme un champ split (une valeur par élément). Ex : fold="handicap_*:handicaps".
fold-dropBooleanfalseAvec fold : retire du résultat les colonnes sources repliées.

Separateurs : rename et replace utilisent le pipe | pour separer les paires, et : pour separer la clé de la valeur. replace-fields utilise le format CHAMP:pattern:remplacement (les 2 premiers : sont des delimiteurs). numeric utilise la virgule. split utilise la virgule entre entrées et champ:séparateur pour chacune (le séparateur à découper est souvent |).

Exemple : trim + renommage

Les données brutes RappelConso contiennent des noms de colonnes longs et techniques. On renomme les colonnes pour la lisibilite et on nettoie les espaces superflus.

Données brutes (JSON)

{
  "modeles_ou_references": " Lot 2024-ABC ",
  "categorie_produit": "Alimentation",
  "marque_produit": "Ma Marque",
  "date_publication": "2024-06-15",
  "motif_rappel": "Presence de Listeria"
}

Apres normalisation

{
  "Produit": "Lot 2024-ABC",       // trim + rename
  "Catégorie": "Alimentation",     // rename
  "Marque": "Ma Marque",           // rename
  "Date": "2024-06-15",            // rename
  "Motif": "Presence de Listeria"  // rename
}

Code

<dsfr-data-source id="raw" api-type="opendatasoft"
  dataset-id="rappelconso-v2-gtin-trie"
  base-url="https://data.economie.gouv.fr"
  server-side page-size="50">
</dsfr-data-source>

<dsfr-data-normalize id="clean" source="raw"
  trim
  rename="modeles_ou_references:Produit | categorie_produit:Catégorie | marque_produit:Marque | date_publication:Date | motif_rappel:Motif">
</dsfr-data-normalize>

<!-- Les données normalisees sont pretes pour query/chart -->
<dsfr-data-list source="clean" pagination="5"></dsfr-data-list>

Exemple : pipeline complet

Source → Normalize → Query → Chart

<dsfr-data-source id="raw" url="..." transform="results"></dsfr-data-source>

<dsfr-data-normalize id="clean" source="raw"
  numeric="population, budget"
  rename="pop_tot:Population totale | lib_dep:Departement"
  trim
  replace="N/A: | n.d.:">
</dsfr-data-normalize>

<dsfr-data-query id="stats" source="clean"
  group-by="Departement"
  aggregate="population:sum"
  order-by="population__sum:desc"
  limit="10">
</dsfr-data-query>

<dsfr-data-chart source="stats" type="bar"
  label-field="Departement"
  value-field="population__sum">
</dsfr-data-chart>

Exemple : remplacement cible par champ (INSEE)

L'API INSEE Melodi retourne des codes de dimension (AGE: "Y30T39", PCS: "3"). replace-fields permet de les decoder sans affecter les autres champs qui pourraient contenir les mêmes valeurs.

<dsfr-data-source id="raw" api-type="insee"
  base-url="https://api.insee.fr/melodi"
  dataset-id="DS_POPULATIONS_REFERENCE"
  where="POPREF_MEASURE:eq:PMUN, TIME_PERIOD:eq:2023">
</dsfr-data-source>

<dsfr-data-normalize id="decoded" source="raw"
  replace-fields="AGE:Y30T39:30-39 ans | AGE:Y_LT30:Moins de 30 ans | PCS:3:Cadres | PCS:5:Employes"
  replace="N/A:">
</dsfr-data-normalize>

<dsfr-data-chart source="decoded" type="bar"
  label-field="AGE" value-field="OBS_VALUE">
</dsfr-data-chart>