cClaude.rocks ☕ Le blog

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

Menu

On trouve facilement des exemples sur des problématiques atomiques sur jq sur de nombreux sites, comme sur StackOverflow, mais il est plus difficile de trouver de la documentation permettant de garder le flux initial.

Ce billet vous propose un exemple ou l’on modifie assez fortement une partie de la structure initiale Ă©tape par Ă©tape.


ඏ

Présentation

  • L’idĂ©e ici est de distribuer des donnĂ©es afin d’aplatir une structure JSON.
  • Pour que l’exemple soit complet, on va Ă©galement :
    • Garder la structure d’origine et donc ne modifier que le sous-arbre que l’on souhaite aplatir,
    • RĂ©cupĂ©rer une valeur ailleurs dans la structure (.zone."@") et l’injecter dans chacune des entrĂ©es crĂ©e.
{
  "types": {
    "A": [
      { "source": "abc", "target": "192.168.1.1" },
      { "source": "xyz", "target": "192.168.1.2" }
    ],
    "NS": [{ "source": "@", "target": "ns1.services.cclaude.rocks." }]
  },
  "zone": {
    "@": "test.cclaude.rocks",
    "Autre": "chose"
  }
}
{
  "types": [
    {
      "type": "A",                  // On reporte le type
      "zone": "test.cclaude.rocks", // On reporte la zone
      "source": "abc",
      "target": "192.168.1.1"
    },
    {
      "type": "A",                  // On reporte le type
      "zone": "test.cclaude.rocks", // On reporte la zone
      "source": "xyz",
      "target": "192.168.1.2"
    },
    {
      "type": "NS",                 // On reporte le type
      "zone": "test.cclaude.rocks", // On reporte la zone
      "source": "@",
      "target": "ns1.services.cclaude.rocks."
    }
  ],
  "zone": {                         // On garde les autres objets tel quel
    "@": "test.cclaude.rocks",
    "Autre": "chose"
  }
}

Premier jet

La premiĂšre chose Ă  faire est Ă  sauvegarder la valeur de l’objet zone que l’on souhaite reporter dans chacune des entrĂ©es du tableau, types:

.zone."@" as $ZONE

Ensuite, dans un premier temps on travaille, uniquement sur ce tableau :

.type

Afin de pouvoir récupérer le nom de la clé et sa valeur associée, on va utiliser la fonction to_entries.

Puisqu’on veut faire un traitement sur l’ensemble des couples (key,value), on va le « dĂ©structurer » Ă  l’aide de []:

to_entries[]

À ce moment, on est dans une boucle : « pour chaque (key,value) faire »

On sauvegarde alors la clé dans $K:

.key as $K

on peut maintenant reconstruire la clé couple (key,value) :

{
  "key": $K,    // On remet la clé
  "value": ...  // Nouvel objet
}

Il faut construire un nouvel objet qui est un tableau, on va donc traiter chaque entrĂ©e du tableau Ă  l’aide de la fonction map() et pour chaque entrĂ©e ajouter la clĂ© comme type et la valeur de $ZONE comme zone :

.value |
  map( { "type": $K, "zone": $ZONE } + . )

Le code complet depuis un terminal bash :

echo '{"types":{"A":[{"source":"abc","target":"192.168.1.1"},{"source":"xyz","target":"192.168.1.2"}],"NS":[{"source":"@","target":"ns1.services.cclaude.rocks."}]},"zone":{"@":"test.cclaude.rocks","Autre":"chose"}}' | jq '.zone."@" as $ZONE | .types | to_entries[] | .key as $K | { "key": $K, "value": (.value | map( { "type": $K, "zone": $ZONE } + . )) }'

Le filtre jq formaté :

.zone."@" as $ZONE |
.types |
to_entries[] |
  .key as $K |
  {
    "key": $K,
    "value": (.value | map( { "type": $K, "zone": $ZONE } + . ))
  }

Le code sur jqplay.org

Voici ce que cette premiÚre version donne :

{
  "key": "A",
  "value": [
    {
      "type": "A",
      "zone": "test.cclaude.rocks",
      "source": "abc",
      "target": "192.168.1.1"
    },
    {
      "type": "A",
      "zone": "test.cclaude.rocks",
      "source": "xyz",
      "target": "192.168.1.2"
    }
  ]
}
{
  "key": "NS",
  "value": [
    {
      "type": "NS",
      "zone": "test.cclaude.rocks",
      "source": "@",
      "target": "ns1.services.cclaude.rocks."
    }
  ]
}

On peut sembler loin du résultat espérer, mais on a fait le plus gros du travail.

On raffine le premier jet avec from_entries

On va maintenant faire l’opĂ©ration inverse de la fonction to_entries Ă  l’aide de from_entries.

Le code complet depuis un terminal bash :

echo '{"types":{"A":[{"source":"abc","target":"192.168.1.1"},{"source":"xyz","target":"192.168.1.2"}],"NS":[{"source":"@","target":"ns1.services.cclaude.rocks."}]},"zone":{"@":"test.cclaude.rocks","Autre":"chose"}}' | jq '.zone."@" as $ZONE | .types | [ to_entries[] | .key as $K | { "key": $K, "value": (.value | map( { "type": $K, "zone": $ZONE } + . )) } ] | from_entries'
.zone."@" as $ZONE |
.types |
[
  to_entries[] |
  .key as $K |
  {
    "key": $K,
    "value": (
      .value |
      map(
        { "type": $K, "zone": $ZONE } + .
      )
    )
  }
] |
from_entries
{
  "A": [
    {
      "type": "A",
      "zone": "test.cclaude.rocks",
      "source": "abc",
      "target": "192.168.1.1"
    },
    {
      "type": "A",
      "zone": "test.cclaude.rocks",
      "source": "xyz",
      "target": "192.168.1.2"
    }
  ],
  "NS": [
    {
      "type": "NS",
      "zone": "test.cclaude.rocks",
      "source": "@",
      "target": "ns1.services.cclaude.rocks."
    }
  ]
}

On optimise un peu le code avec with_entries

On remarque la sĂ©quence [ to_entries[] | 
 | from_entries ] hors c’est justement ce que fait la fonction with_entries( 
 ), on va donc simplement remplacer la sĂ©quence par cette fonction.

Le code complet depuis un terminal bash :

echo '{"types":{"A":[{"source":"abc","target":"192.168.1.1"},{"source":"xyz","target":"192.168.1.2"}],"NS":[{"source":"@","target":"ns1.services.cclaude.rocks."}]},"zone":{"@":"test.cclaude.rocks","Autre":"chose"}}' | jq '.zone."@" as $ZONE | .types | with_entries( .key as $K | { "key": $K, "value": (.value | map( { "type": $K, "zone": $ZONE } + . )) })'

Le filtre jq formaté :

.zone."@" as $ZONE |
.types |
with_entries(
  .key as $K |
  {
    "key": $K,
    "value": (
      .value |
      map(
        { "type": $K, "zone": $ZONE } + .
      )
    )
  }
)

Le résultat est inchangé, voir sur jqplay.org.

đŸ‘©đŸŸâ€đŸŽ“

La séquence

[ to_entries[] | 
 | from_entries ]

peut s’écrire avec la fonction

with_entries( 
 )

Affinage du tableau

Maintenant, il reste une partie un peu délicate qui consiste à supprimer le niveau juste dessous .types et à mettre tous les éléments des sous tableaux dans un seul tableau.

Pour cela juste aprÚs le filtre .types on va créer un tableau, mettre tout le code restant dans ce tableau :

.types | [ 
 ]

AprÚs la fonction with_entries, on va faire un to_entries et « déstructurer » 2 niveaux de tableau :

.types | [ with_entries( 
 ) | to_entries[].value[] ]

Le code complet depuis un terminal bash :

echo '{"types":{"A":[{"source":"abc","target":"192.168.1.1"},{"source":"xyz","target":"192.168.1.2"}],"NS":[{"source":"@","target":"ns1.services.cclaude.rocks."}]},"zone":{"@":"test.cclaude.rocks","Autre":"chose"}}' | jq '.zone."@" as $ZONE | .types | [ with_entries( .key as $K | { "key": $K, "value": (.value | map( { "type": $K, "zone": $ZONE } + . )) }) | to_entries[].value[] ]'

Le filtre jq formaté :

.zone."@" as $ZONE |
.types |
[
  with_entries(
    .key as $K |
    {
      "key": $K,
      "value": (
        .value |
        map(
          { "type": $K, "zone": $ZONE } + .
        )
      )
    }
  ) | to_entries[].value[]
]
[
  {
    "type": "A",
    "zone": "test.cclaude.rocks",
    "source": "abc",
    "target": "192.168.1.1"
  },
  {
    "type": "A",
    "zone": "test.cclaude.rocks",
    "source": "xyz",
    "target": "192.168.1.2"
  },
  {
    "type": "NS",
    "zone": "test.cclaude.rocks",
    "source": "@",
    "target": "ns1.services.cclaude.rocks."
  }
]

Le code final

Il ne reste plus qu’à faire la mĂȘme chose tout en conservant la structure initiale :

L’astuce consiste tout simplement Ă  affecter le rĂ©sultat obtenu plus haut comme suit (sans omettre les parenthĂšses) :

.types=( 
 )

Le code complet depuis un terminal bash :

echo '{"types":{"A":[{"source":"abc","target":"192.168.1.1"},{"source":"xyz","target":"192.168.1.2"}],"NS":[{"source":"@","target":"ns1.services.cclaude.rocks."}]},"zone":{"@":"test.cclaude.rocks","Autre":"chose"}}' | jq '.zone."@" as $ZONE | .types=(.types | [ with_entries( .key as $K | { "key": $K, "value": (.value | map( { "type": $K, "zone": $ZONE } + . )) }) | to_entries[].value[] ])'

Le filtre jq formaté :

.zone."@" as $ZONE |
.types=(
  .types |
  [
    with_entries(
      .key as $K |
      {
        "key": $K,
        "value": (
          .value |
          map(
            { "type": $K, "zone": $ZONE } + .
          )
        )
      }
    ) |
    to_entries[].value[]
  ]
)
{
  "types": [
    {
      "type": "A",
      "zone": "test.cclaude.rocks",
      "source": "abc",
      "target": "192.168.1.1"
    },
    {
      "type": "A",
      "zone": "test.cclaude.rocks",
      "source": "xyz",
      "target": "192.168.1.2"
    },
    {
      "type": "NS",
      "zone": "test.cclaude.rocks",
      "source": "@",
      "target": "ns1.services.cclaude.rocks."
    }
  ],
  "zone": {
    "@": "test.cclaude.rocks",
    "Autre": "chose"
  }
}

ඏ

Liens

  • Les fonctions to_entries,from_entries et with_entries expliquĂ©es dans le manuel de JQ,
  • La FAQ de jq en anglais,
  • Le manuel de jq en anglais (Vous pouvez choisir la version de jq).

኿


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