web-dev-qa-db-fra.com

jq compte le nombre d'éléments dans json par une clé spécifique

Voici les deux premiers éléments de mon fichier json

{
"ReferringUrl": "N",
"OpenAccess": "0",
"Properties": {
    "ItmId": "1694738780"
   }
}
{
"ReferringUrl": "L",
"OpenAccess": "1",
"Properties": {
    "ItmId": "1347809133"
  }
}

Je veux compter le nombre d'articles par chaque ItmId apparu dans le json. Par exemple, les éléments avec "ItmId" 1694738780 apparaissent 10 fois et les éléments avec "ItmId" 1347809133 apparaissent 14 fois dans mon fichier json. Puis retourne un json comme celui-ci

{"ItemId": "1694738780",
 "Count":  10
}
{"ItemId": "1347809133",
 "Count":  14
}

J'utilise bash. Et je préfère le faire totalement par jq. Mais c'est correct d'utiliser une autre méthode.

Je vous remercie!!!

9
Eleanor

Voici une solution (en supposant que l'entrée est un flux d'objets JSON valides) et que vous appelez jq avec l'option -s:

map({ItemId: .Properties.ItmId})             # extract the ItmID values
| group_by(.ItemId)                          # group by "ItemId"
| map({ItemId: .[0].ItemId, Count: length})  # store the counts
| .[]                                        # convert to a stream

Une approche légèrement plus efficace en mémoire consisterait à utiliser inputs si votre jq en dispose; mais dans ce cas, utilisez -n au lieu de -s et remplacez la première ligne ci-dessus par: [entrées | {ItemId: .Properties.ItmId}]

Solution efficace

Les solutions ci-dessus utilisent le group_by Intégré, ce qui est pratique mais conduit à des inefficients facilement évités. L'utilisation du counter suivant facilite l'écriture d'une solution très efficace:

def counter(stream):
  reduce stream as $s ({}; .[$s|tostring] += 1);

Utilisation de l'option de ligne de commande -n, et appliqué comme suit:

counter(inputs | .Properties.ItmId)

cela conduit à un dictionnaire des comptes:

{
  "1694738780": 1,
  "1347809133": 1
}

Un tel dictionnaire est probablement plus utile qu'un flux d'objets singleton comme envisagé par l'OP, mais si un tel flux est nécessaire, on peut modifier ce qui précède comme suit:

counter(inputs | .Properties.ItmId)
| to_entries[]
| {ItemId: (.key), Count: .value}
11
peak

Utilisation de la commande jq

cat json.txt | jq '.Properties .ItmId' | sort | uniq -c | awk -F " " '{print "{\"ItmId\":" $2 ",\"count\":" $1"}"}'| jq .
3
skr

Voici une variante utilisant réduire, setpath et getpath pour faire l'agrégation et to_entries pour faire le formatage final qui suppose que vous exécutez jq en tant que

jq --Slurp -f query.jq < data.json

data.json contient vos données et query.jq contient

  map(.Properties.ItmId)
| reduce .[] as $i (
    {}; setpath([$i]; getpath([$i]) + 1)
  )
| to_entries | .[] | { "ItemId": .key, "Count": .value }
1
jq170727

Voici une solution super efficace - en particulier, aucun tri n'est requis. L'implémentation suivante nécessite une version de jq avec inputs mais il est facile d'adapter le programme pour utiliser des versions antérieures de jq. N'oubliez pas d'utiliser l'option de ligne de commande -n si vous utilisez ce qui suit:

# Count the occurrences of distinct values of (stream|tostring).
# To avoid unwanted collisions, or to recover the exact values,
# consider using tojson
def counter(stream):
  reduce stream as $s ({}; .[$s|tostring] += 1);

counter(inputs | .Properties.ItmId)
| to_entries[]
| {ItemId: (.key), Count: .value}
1
peak