Tracks
/
jq
jq
/
Übungen
/
Einkaufsliste
Einkaufsliste

Einkaufsliste

Lernübung

Einführung

Grundlagen

Wir gehen davon aus, dass du mit der JSON-Syntax und den Datentypen vertraut bist.

jq funktioniert, indem die eingehenden JSON-Daten durch einen einzigen Ausdruck (geschrieben als Pipeline aus Filtern) geleitet werden, um die gewünschten transformierten Daten zu erhalten.

Note

Die Sprache jq wird durch das jq-Programm umgesetzt. Dieses Programm bietet mehrere praktische Kommandozeilenoptionen, mit denen du steuern kannst, wie die Eingabe verarbeitet und die Ausgabe dargestellt wird.

In den folgenden Beispielen wirst du auf Folgendes stoßen:

  • -n oder --null-input

    Normalerweise wird dem jq-Programm eine Datei zum Lesen übergeben, oder du sendest Daten an seine Eingabe. Die Option --null-input ermöglicht es dir, JSON-Daten ohne Eingaben zu erzeugen.

  • -c oder --compact-output

    jq gibt seine Ausgabe standardmäßig formatiert aus. Für Menschen ist es äußerst nützlich, die Daten anzusehen, wenn sie schön formatiert sind. Für Maschinen ist das jedoch nicht nötig: Die Option --compact-output entfernt die Formatierungsleerzeichen, um die Größe des resultierenden JSON zu minimieren.

  • -f filename oder --from-file filename

    Lies das jq-Programm aus filename, anstatt es in der Kommandozeile anzugeben. sed und awk verwenden die Option -f für denselben Zweck. Du wirst dies in den Testskripten für die Übungen sehen.

Der Rest dieser Lektion konzentriert sich auf die Sprache jq.

Filter und Pipes

Filter werden auch als Ausdrücke bezeichnet.

Ein Filter nimmt eine Eingabe entgegen und erzeugt eine Ausgabe. Ähnlich wie in einer Unix-Shell kannst du Filter mit einer Pipe | verbinden, um die Ausgabe eines Filters mit der Eingabe eines anderen zu verbinden.

Der Identitätsfilter: .

Dies ist der einfachste Filter. Er gibt einfach seine Eingabe an seine Ausgabe weiter.

$ echo '[1, 2, 3]' | jq '.'
[
  1,
  2,
  3
]

Arrays

Dies ist eine kurze Einführung in die Arbeit mit Arrays. Wir werden dieses Thema später ausführlicher behandeln.

Auf Array-Elemente wird mit Klammern zugegriffen, und sie sind nullbasiert indiziert.

$ echo '[10, 20, 30]' | jq '.[1]'
20

Ein Filter kann ein Array erstellen, indem er einen Ausdruck in [ und ] einschließt

  • mit einer bekannten Liste von Elementen:

    jq -n '[1, 2, 3]'
    
  • um einen Strom von Elementen zu sammeln: Zum Beispiel ist range eine Funktion, die einen Strom von Zahlen ausgibt

    $ jq -n 'range(10; 70; 15)'
    10
    25
    40
    55
    

    Die Verwendung von [] sammelt die Ergebnisse des Ausdrucks in einem Array

    $ jq -c -n '[range(10; 70; 15)]'
    [10,25,40,55]
    

Das Komma ist ein Operator

Das Komma ist nicht nur Syntax, die Array-Elemente trennt. Komma ist ein Operator, der Ströme verbindet.

Zum Beispiel ist [1, 2, 3] ein Filter, der den Array-Konstruktor [] verwendet, um das Ergebnis der Verknüpfung der drei Ausdrücke 1, 2 und 3 zu sammeln.

Hast du die Semikolons in range(10; 70; 15) oben bemerkt? Weil Kommas in der Sprache jq einen bestimmten Zweck haben, verwenden Funktionen, die mehrere Argumente annehmen, Semikolons, um die Argumente zu trennen.

Objekte

Eine kurze Einführung in Objekte.

Ähnlich wie in vielen Programmiersprachen verwendest du Punkte, um auf Objekt-Eigenschaften zuzugreifen

$ echo '{"foo": {"bar": "qux"}}' | jq '.foo.bar'
"qux"
Note

Klammern können auch für Objekte verwendet werden, aber dann werden Anführungszeichen für Zeichenkettenliterale benötigt. Dies ist eine Methode, um mit Schlüsseln zu arbeiten, die Leerzeichen enthalten.

$ echo '{"foo bar": "qux"}' | jq '.["foo bar"]'
"qux"

Du kannst ein Objekt mit {} und key: value-Paaren erstellen. Anführungszeichen sind bei Schlüsseln, die „einfache“ Strings sind, nicht erforderlich.

jq -n '{question: (6 * 9), answer: 42}'

gibt aus

{
  "question": 54,
  "answer": 42
}

Um den Schlüssel als Ausdruck zu behandeln, musst du ihn in runde Klammern setzen (das Folgende gibt ebenfalls dasselbe wie oben aus).

echo '[{"key":"question", "value":54}, {"key":"answer", "value":42}]' \
| jq '{(.[0].key): .[0].value, (.[1].key): .[1].value}'

Pipelines

Zum Beispiel, gegeben file.json mit

{
  "key1": "value1",
  "key2": [5, 15, 25]
}

Berechnen wir die Länge des Array key2:

$ jq '.key2 | length' file.json
3

Wir leiten die Ausgabe des Ausdrucks .key2 als Eingabe an length weiter, was wenig überraschend die Anzahl der Elemente im Array ausgibt.

Filter können ihre Eingabe ignorieren

In diesem Beispiel werden die eingehenden JSON-Daten ignoriert und haben keinen Einfluss auf die Ausgabe:

$ echo '{"answer": 42}' | jq '6 * 9'
54

Filter können Datenströme ausgeben

Ein Filter kann mehr als einen Wert ausgeben. Zum Beispiel gibt der Filter .[] jedes Element eines Arrays als separaten Wert aus:

$ jq -n -c '[1, 2, 3]'
[1,2,3]

$ jq -n -c '[1, 2, 3] | .[]'
1
2
3

Wenn du einen solchen Filter in einen anderen leitest, wird der zweite Filter für jeden Wert ausgeführt:

$ jq -n -c '[1, 2, 3] | .[] | . * 2'
2
4
6

Dies ist wie eine implizite Iteration. Sobald du diese Technik verstehst, wirst du erkennen, dass sehr mächtige jq-Filter sehr prägnant sein können.

Runde Klammern

Runde Klammern werden verwendet, um Teilausdrücke zusammenzufassen und die Reihenfolge der Operationen zu erzwingen, genau wie in anderen Sprachen. In jq kann der Bedarf dafür etwas überraschend erscheinen.

Zum Beispiel wollen wir ein Array mit 2 Elementen erstellen: die Quadratwurzel aus 9 und e hoch 1. Die beiden einzelnen Ausdrücke sind 9 | sqrt und 1 | exp. Wir erwarten, dass die Ausgabe das Array [3, 2.7...] ist

$ jq -n '[ 9|sqrt, 1|exp ]'
[
  20.085536923187668,
  2.718281828459045
]

Warum haben wir nicht bekommen, was wir erwartet haben? jq interpretiert das so:

[ ((9|sqrt), 1) | exp ]

jq erstellt einen Strom aus zwei Elementen (3 und 1), die jeweils an exp übergeben werden.

Wir müssen sicherstellen, dass exp nur eine Zahl als Eingabe erhält. Mit anderen Worten: Wir müssen erzwingen, dass die Pipe vor dem Komma ausgewertet wird.

$ jq -n '[ 9|sqrt, (1|exp) ]'
[
  3,
  2.718281828459045
]

Funktionen und Operatoren

Ohne zu sehr ins Detail zu gehen (Funktionen sind Thema einer anderen Übung), hier einige nützliche eingebaute Funktionen:

  • length

    Wenn ein Array als Eingabe gegeben ist, gib die Anzahl der Elemente im Array aus.

    $ jq -n '[10, 20, 30, 40] | length'
    4
    
  • +

    Dieser Operator tut je nach Typ seiner Operanden unterschiedliche Dinge: Er addiert Zahlen, verkettet Strings, hängt Arrays an, führt Objekte zusammen.

    $ jq -c -n '
        3 + 4,
        "foo" + "bar",
        ["a", "b"] + ["c"],
        {"m": 10} + {"n": 20}
    '
    7
    "foobar"
    ["a","b","c"]
    {"m":10,"n":20}
    

    add ist eine Funktion, die ein Array entgegennimmt und ein Element zurückgibt, bei dem alle Elemente nach den Regeln von + zusammengefügt wurden. [1, 2, 3] | add gibt 6 aus.

  • map

    Wenn ein Array als Eingabe und ein Filter als Argument gegeben ist, gib ein Array aus, bei dem der Filter auf jedes Element angewendet wird

    $ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
    [2,4,6,8]
    
  • select

    Wenn eine Eingabe und ein Filter als Argument gegeben sind:

    • wenn der auf das Argument angewendete Filter einen wahren Wert ergibt, gib die Eingabe unverändert aus
    • andernfalls gib nichts aus (nicht den Wert null, wirklich keine Ausgabe)

    Zum Beispiel: Wähle aus einigen Zahlen die durch 3 teilbaren aus

    $ jq -n 'range(10) | select(. % 3 == 0)'
    0
    3
    6
    9
    

    Erinnere dich, dass range einen Strom von Zahlen ausgibt. select wird für jede Zahl einmal aufgerufen. Nur die Zahlen, die den Ausdruck „bestehen“, werden ausgegeben.

    Oft musst du Elemente eines Arrays auswählen. Es gibt ein paar Möglichkeiten, dies zu tun.

    Wähle bei der Eingabe ["Anne", "Bob", "Cathy", "Dave"] die Namen mit der Länge 4 aus.

    • verwende map und select zusammen

      map(select(length == 4))
      
    • zerlege das Array in Elemente, wende select auf diesen Strom an und sammle die Ergebnisse

      [ .[] | select(length == 4) ]
      

Kommentare

Kommentare beginnen mit dem Zeichen # und reichen bis zum Ende der Zeile.

Anleitung

Du hast plötzlich Lust auf Pfannkuchen. Als moderner, technisch interessierter Mensch schreibst du eine Einkaufsliste für Pfannkuchen-Zutaten im JSON-Format. Die Struktur der Liste sieht so aus:

{
  "name": "name of shopping list",
  "ingredients": [ ...list of ingredients... ],
  "optional ingredients": [ ...list of ingredients... ]
}

Eine Zutat wird als JSON-Objekt dargestellt, etwa so:

{
  "item": "flour",
  "amount": {
    "quantity": 2,
    "unit": "cup"
  }
}

Eine Zutat hat eine optionale Eigenschaft namens "substitute", die einen String mit einem anderen Element enthält, das stattdessen verwendet werden kann.

Schauen wir uns an, wie wir die Einkaufsliste mit jq untersuchen können.

1. Extrahiere den Namen der Einkaufsliste

Schreibe einen Ausdruck, der das Element "name" der Einkaufsliste ausgibt.

2. Zähle, wie viele Zutaten benötigt werden

Zähle nur die benötigten Zutaten. Die optionalen Zutaten zählst du nicht mit.

3. Wie viel Zucker?

Schreibe einen Ausdruck, der die Menge Zucker ausgibt. Gewünscht ist nur der numerische Teil.

4. Bilde die Zutaten ab, die ersetzt werden können

Einige Zutaten können ersetzt werden (wenn du Zutat X nicht hast, kannst du Y verwenden). Gib ein JSON-Objekt aus, das die empfohlene Zutat auf ihren Ersatz abbildet. Nimm die optionalen Zutaten in die Abbildung mit auf.

Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
jq Exercism

Bereit, mit Einkaufsliste zu starten?

Melde dich bei Exercism an, um jq mit 12 Konzepte75 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.