cClaude.rocks ☕ Le blog

[Nouvelles technologies du libre, sciences et coups de gueule…]

Menu

Afin de rĂ©duire la taille de certains fichiers JSON, il peut ĂȘtre intĂ©ressant d’utiliser la notion de valeur par dĂ©faut.

On va voir comment utiliser un filtre jq pour faire cela Ă  moindres frais.


ඏ

Présentation de la problématique

Imaginons le fichier JSON suivant : (fichier1.json)

{
  "context1": {
    "config": {
      "cle1": "valeur-context-1",
      "cle2": "valeur-commune-2",
      "cle3": "valeur-commune-3",
      "cle4": "valeur-commune-4",
      "cle5": "valeur-commune-5"
    }
  },
  "context2": {
    "config": {
      "cle1": "valeur-commune-1",
      "cle2": "valeur-context-2",
      "cle3": "valeur-commune-3",
      "cle4": "valeur-commune-4"
    }
  },
  "context3": {
    "config": {
      "cle1": "valeur-commune-1",
      "cle2": "valeur-commune-2",
      "cle3": "valeur-commune-3",
      "cle4": "valeur-commune-4"
    }
  }
}

On constate qu’il est possible d’avoir la mĂȘme information comme suit : (fichier2.json)

{
  "config": {
    "cle1": "valeur-commune-1",
    "cle2": "valeur-commune-2",
    "cle3": "valeur-commune-3",
    "cle4": "valeur-commune-4"
  },
  "context1": {
    "config": {
      "cle1": "valeur-context-1",
      "cle5": "valeur-commune-5"
    }
  },
  "context2": {
    "config": {
      "cle2": "valeur-context-2"
    }
  },
  "context3": {
    "config": {}
  }
}

Cette version est plus courte, mais Ă©galement plus claire, puisqu’elle met en Ă©vidence les valeurs communes. On dira qu’on a factorisĂ© la configuration.

Dans le premier cas, pour obtenir la configuration d’un « contexte », on Ă©crira : đŸ•čjq-play

jq '.context1.config' fichier1.json
{
  "cle1": "valeur-context-1",
  "cle2": "valeur-commune-2",
  "cle3": "valeur-commune-3",
  "cle4": "valeur-commune-4"
}

Comment obtenir le mĂȘme rĂ©sultat Ă  partir du second fichier ? đŸ•čjq-play

jq '.config + .context1.config' fichier2.json

On peut reconstruire la structure initiale, comme suit : đŸ•čjq-play

jq '.config as $D | { context1: { config: ($D + .context1.config) } }' fichier2.json
{
  "context1": {
    "config": {
      "cle1": "valeur-context-1",
      "cle2": "valeur-commune-2",
      "cle3": "valeur-commune-3",
      "cle4": "valeur-commune-4"
    }
  }
}

Pour reconstruire tout le fichier : đŸ•čjq-play

jq '.config as $D |
{
  context1: { config: ($D + .context1.config) },
  context2: { config: ($D + .context2.config) },
  context3: { config: ($D + .context3.config) }
}' fichier2.json
  • Explications

    Détaillons le filtre jq de ce dernier cas qui utilise le mot clé as :

    .config as $D |
    {
      context1: { config: ($D + .context1.config) },
      context2: { config: ($D + .context2.config) },
      context3: { config: ($D + .context3.config) }
    }
    
    • .config as $D : On applique le filtre .config et on le met dans la variable $D (du coup, le flux courant reste inchangĂ©)
    • `|' : Le flux courant restant inchangĂ©, on continue avec fichier initial.
    • { 
 } : On dĂ©finit un nouvel objet
    • context1: { config: 
 }, 
 : On reconstruit la structure
    • ($D + .context1.config) : pour la valeur de chaque clĂ© .config on va prendre le contenu de $D et on ajoute ou modifie + avec l’objet .context.config du contexte courant.

ඏ

GĂ©nĂ©ralisons au cas oĂč il y a d’autres valeurs dans le fichier

Si vous Ă©crivez un nouveau code, peut-ĂȘtre le plus simple sera de prendre en compte cette notion de valeur par dĂ©faut, mais dans un traitement existant, il faut mieux revenir au format initial.

Pour l’exemple, on ajoutera uniquement une clĂ© name avec sa valeur, mais cela peut ĂȘtre quelque chose de beaucoup plus compliquĂ©.

Les données complÚtes : (fichier11.json)

{
  "context1": {
    "config": {
      "cle1": "valeur-context-1",
      "cle2": "valeur-commune-2",
      "cle3": "valeur-commune-3",
      "cle4": "valeur-commune-4"
    },
    "name": "context-1"
  },
  "context2": {
    "config": {
      "cle1": "valeur-commune-1",
      "cle2": "valeur-context-2",
      "cle3": "valeur-commune-3",
      "cle4": "valeur-commune-4"
    },
    "name": "context-2"
  },
  "context3": {
    "config": {
      "cle1": "valeur-commune-1",
      "cle2": "valeur-commune-2",
      "cle3": "valeur-commune-3",
      "cle4": "valeur-commune-4"
    }
  },
  "name": "context-3"
}

Les données misent en facteur : (fichier12.json)

{
  "config": {
    "cle1": "valeur-commune-1",
    "cle2": "valeur-commune-2",
    "cle3": "valeur-commune-3",
    "cle4": "valeur-commune-4"
  },
  "context1": {
    "config": {
      "cle1": "valeur-context-1"
    },
    "name": "context-1"
  },
  "context2": {
    "config": {
      "cle2": "valeur-context-2"
    },
    "name": "context-2"
  },
  "context3": {
    "config": {},
    "name": "context-3"
  }
}

Pour reconstruire le premier fichier Ă  partir du second, on utilisera : đŸ•čjq-play

jq '.config as $D |
  del( .config ) |
  .context1.config = ($D + .context1.config) |
  .context2.config = ($D + .context2.config) |
  .context3.config = ($D + .context3.config)
' fichier12.json
  • Explications

    Détaillons le filtre jq :

    .config as $D |
      del( .config ) |
      .context1.config = ($D + .context1.config) |
      .context2.config = ($D + .context2.config) |
      .context3.config = ($D + .context3.config)
    
    • .config as $D : On applique le filtre .config et on le met dans la variable $D (du coup, le flux courant reste inchangĂ©)
    • `|' : Le flux courant restant inchangĂ©, on continue avec fichier initial.
    • .context1.config = 
 : On applique une nouvelle valeur Ă  l’objet .context1.config.
    • ($D + .context1.config) : pour la valeur de chaque clĂ© .config on va prendre le contenu de $D et on ajoute ou modifie + avec l’objet .context.config du contexte courant.
    • et on reproduit pour les 2 autres objets .context2 et .context3.

ඏ

Généralisation aux tableaux

Dans les exemples prĂ©cĂ©dant, le choix de l’utilisation de .context1, .context2, et .context3 n’est probablement pas une bonne idĂ©e.
On va donc regarder cela pour le cas oĂč l’on souhaite factoriser pour un tableau.

Considérons ce fichier : (fichier22.json)

{
  "config": {
    "cle1": "valeur-commune-1",
    "cle2": "valeur-commune-2",
    "cle3": "valeur-commune-3",
    "cle4": "valeur-commune-4"
  },
  "contexts": [
    {
      "config": {
        "cle1": "valeur-context-1"
      },
      "name": "context-1"
    },
    {
      "config": {
        "cle2": "valeur-context-2"
      },
      "name": "context-2"
    },
    {
      "config": {},
      "name": "context-3"
    }
  ]
}

Dans ce cas, on a un tableau de contextes, on peut obtenir toutes les configurations avec un code assez compactes, mais en perdant les autres valeurs (cependant, cela peut ĂȘtre suffisant pour votre cas d’usage). đŸ•čjq-play

jq '.config as $D | [ $D + .contexts[].config ]' fichier22.json

Mais on peut facilement reconstruire le fichier complet Ă  l’aide de : đŸ•čjq-play

jq '.config as $D |
  del( .config ) | .contexts[] | [ .config = ($D + .config) ]
' fichier22.json

ඏ

Factorisation sur plusieurs niveaux

Imaginons que l’on ait la structure suivante : (fichier32.json)

{
  "config": {
    "cle1": "valeur-globale-1",
    "cle2": "valeur-globale-2",
    "cle3": null
  },
  "context1": {
    "config": {
      "cle1": "valeur-context-1",
      "cle2": null,
      "cle4": "valeur-context-4"
    },
    "name": "context-1",
    "sous-contexts": [
      {
        "config": {
          "cle1": "sous-valeur-context-1"
        },
        "name": "sous-context-1"
      },
      {
        "config": {
          "cle3": "sous-valeur-context-3"
        },
        "name": "sous-context-2"
      },
      {
        "config": {},
        "name": "sous-context-3"
      }
    ]
  },
  "context2": {
    "config": {
      "cle2": "valeur-context-2"
    },
    "name": "context-2",
    "sous-contexts": [
      {
        "config": {},
        "name": "sous-context-1"
      },
      {
        "config": {
          "cle2": "valeur-sous-context-2"
        },
        "name": "sous-context-2"
      }
    ]
  }
}

Les configurations du second contexte s’obtiennent comme suit : đŸ•čjq-play

jq '(.context2.config + .config ) as $config | [ .context2."sous-contexts"[] | $config + .config ]' fichier32.json

Avec un peu de bash, on peut avoir toutes les configurations :

for C in context1 context2 ; do
  jq --arg context "$C" '(.[$context].config + .config ) as $config | [ .[$context]."sous-contexts"[] | $config + .config ]' fichier32.json
done

Pour obtenir une configuration donnĂ©e : đŸ•čjq-play

jq '(.context2.config + .config ) as $config | .context2."sous-contexts"[] | select( .name =="sous-context-2" ) | $config + .config' fichier32.json

On peut généraliser :

function show_config {
  local -r objet_context="$1"
  local -r nom_sous_context="$2"

  jq --arg context "${objet_context}" \
     --arg name "${nom_sous_context}" '
(.context2.config + .config ) as $config | .context2."sous-contexts"[] | select( .name == $name ) | $config + .config
' fichier32.json
}

show_config 'context2' 'sous-context-2'

Ce qui dans notre cas donnera :

{
  "cle2": "valeur-sous-context-2",
  "cle1": "valeur-globale-1",
  "cle3": null
}

On peut éventuellement améliorer le filtre pour supprimer les valeurs à null en ajoutant :


 | [to_entries[] | select( .value != null )] | from_entries
đŸ˜”

Je me suis cassé les dents sur la création du fichier « dé-factorisé » avec du code jq pur, si vous avez une piste élégante, je vous invite à laisser un commentaire.


ඏ

Liens

኿


â„č 2006 - 2024 | 🏠 Accueil du domaine | 🏡 Accueil du blog