Wie finde und ersetze ich mehrere Feldwerte mit jq?

9

In der folgenden JSON-Datei

{
  "email": "xxx",
  "pass": "yyy",
  "contact": [
    {
      "id": 111,
      "name": "AAA"
    }
  ],
  "lname": "YYY",
  "name": "AAA",
   "group": [
    {
      "name": "AAA",
      "lname": "YYY",
    }
  ],

Ich muss nach dem Schlüssel "Name" suchen und seinen Wert an allen Stellen durch "XXX" ersetzen. Welcher jq-Befehl macht das?

user2181698
quelle

Antworten:

9

Verwendung jqbasierend auf der walkFunktion (benötigt eine aktuelle Version):

jq 'walk(.name?="XXX")' file

Wenn Sie jqdie walkFunktion nicht unterstützen , definieren Sie sie einfach als:

jq '
  # Apply f to composite entities recursively, and to atoms
  def walk(f):
    . as $in
    | if type == "object" then
       reduce keys[] as $key
         ( {}; . + { ($key):  ($in[$key] | walk(f)) } ) | f
    elif type == "array" then map( walk(f) ) | f
    else f
    end;
  walk(.name?="XXX")
' file

Credits: https://github.com/stedolan/jq/issues/963

oliv
quelle
Hallo, ich bekomme folgenden Fehler. Fehler: Ungültiger
Zeichenlauf
@ user2181698 Sind Sie sicher, dass Sie diesen Befehl unter Linux / Unix ausführen?
Qubert
Vielleicht verwendet @ user2181698 eine ungewöhnliche Shell und hat es versäumt, sie zu erwähnen?
Michael Hampton
8

Die Zuweisungsoperationen von jq können eine Aktualisierung an so vielen Orten gleichzeitig durchführen, wie Sie benennen können, und sind für diese Art von Situation vorgesehen. Sie können verwenden

jq '(.. | .name?) |= "XXXX"'

auf jedes Feld mit dem Namen „name“ überall und ersetzen Sie den Wert in jedem auf einmal finden mit „XXXX“ und gibt das resultierende Objekt zurück .

Dies ist nur das ..|.a?Beispiel aus der Dokumentation zum rekursiven Abstieg in Kombination mit der Aktualisierungszuweisung .

Es nutzt den rekursiven Abstieg Operator.. jeden einzelnen Wert im Baum zu finden, zieht dann das Feld „Namen“ von jedem von ihnen mit.name , unterdrückt Fehler von nicht passenden Werten mit? und aktualisiert dann das Objekt in allen diesen Orten auf einmal mit "XXXX" unter Verwendung des Aktualisierungszuweisungsoperators|= und gibt das neue Objekt aus.

Dies funktioniert unabhängig von der Dateistruktur und aktualisiert jedes Namensfeld überall.


Wenn die Datei immer diese Struktur hat und es sich um die bestimmten "Namens" -Felder handelt, die Sie ändern möchten, und nicht nur um einen alten Namen, können Sie sie auch einfach auflisten und ihnen auch als Gruppe zuweisen:

jq '(.name, .contact[].name, .group[].name) |= "XXXX"'

Dies macht die gleiche Zuordnung zu

  1. das Feld "Name" des Objekts der obersten Ebene;
  2. das Feld "Name" jedes Objekts im Array "Kontakt"; und
  3. Das Feld "Name" jedes Objekts im Array "Gruppe".

alles auf einmal. Dies ist besonders nützlich, wenn die Datei möglicherweise andere Namensfelder enthält, die nichts mit Ihnen zu tun haben und die Sie nicht ändern möchten. Es werden nur die drei dort genannten Standorte gefunden und alle gleichzeitig aktualisiert.


Wenn der Wert nur ein Literal ist, wie es hier ist, dann funktioniert auch die einfache Zuordnung mit= und speichert Ihnen ein Zeichen: (..|.name?)="XXXX"- Sie möchten dies auch, wenn Ihr Wert basierend auf dem gesamten Objekt der obersten Ebene berechnet wird. Wenn stattdessen wollen Sie den neuen Namen auf der alten Basis zu berechnen, Sie müssen zu verwenden |=. Wenn ich nicht sicher bin, was ich verwenden soll, |=hat sich das Verhalten in den Eckfällen im Allgemeinen etwas besser.

Wenn Sie mehrere Ersetzungen durchführen müssen , können Sie diese zusammen leiten:

jq '(..|.name?) = "XXXX" | (..|.lname?) = "1234"'

aktualisiert überall die Felder "name" und "lname" und gibt das gesamte aktualisierte Objekt einmal aus.


Einige andere Ansätze, die funktionieren könnten:

  • Sie können auch sehr explizit angeben, mit was Sie auswählen

    (..|objects|select(has("name"))).name |= "XXXX"`
    

    Das findet alles, dann nur die Objekte, dann nur die Objekte, die einen "Namen" haben, dann das Namensfeld für diese Objekte und führt die gleiche Aktualisierung wie zuvor durch.

  • Wenn Sie die Entwicklungsversion von jq ausführen (unwahrscheinlich), kann die walkFunktion auch folgende Aufgaben ausführen : walk(.name?="XXXX"). Alle anderen Versionen funktionieren mit der neuesten Version 1.5.
  • Ein alternatives Multi-Update könnte sein

    jq '(..|has("name")?) += {name: "XXXX", lname: "1234"}'
    

    Das findet alles mit einem Namen und setzt dann sowohl "name" als auch "lname" für jedes Objekt unter Verwendung der arithmetischen Aktualisierungszuweisung*= und des Zusammenführungsverhaltens +für Objekte .

Michael Homer
quelle
1

alternativ jtcbasierte Lösung:

bash $ jtc -w'<name>l+0' -u'"XXX"' your.json 
{
   "contact": [
      {
         "id": 111,
         "name": "XXX"
      }
   ],
   "email": "xxx",
   "group": [
      {
         "lname": "YYY",
         "name": "XXX"
      }
   ],
   "lname": "YYY",
   "name": "XXX",
   "pass": "yyy"
}
bash $ 
Dmitry L.
quelle