Обновите значения одного файла JSON значениями из другого JSON, используя JQ (на всех уровнях) - PullRequest
0 голосов
/ 17 октября 2018

У меня есть два файла JSON:

source.json:

{
  "general": {
    "level1": {
      "key1": "x-x-x-x-x-x-x-x",
      "key3": "z-z-z-z-z-z-z-z",
      "key4": "w-w-w-w-w-w-w-w"
    },
    "another" : {
      "key": "123456",
      "comments": {
        "one": "111",
        "other": "222"
      }
    }
  },
  "title": "The best"
}

и цель

.json:

{
  "general": {
    "level1": {
      "key1": "xxxxxxxx",
      "key2": "yyyyyyyy",
      "key3": "zzzzzzzz"
    },
    "onemore": {
      "kkeeyy": "0000000"
    }
  },
  "specific": {
    "stuff": "test"
  },
  "title": {
    "one": "one title",
    "other": "other title"
  }
}

Мне нужны все значения ключей, которые существуют в обоих файлах, скопированы из source.json в target.json , учитываявсе уровни.
Я видел и тестировал решение из этого поста .Он копирует только первый уровень ключей, и я не мог заставить его делать то, что мне нужно.Результат решения в этой записи выглядит следующим образом:

{
  "general": {
    "level1": {
      "key1": "x-x-x-x-x-x-x-x",
      "key3": "z-z-z-z-z-z-z-z",
      "key4": "w-w-w-w-w-w-w-w"
    },
    "another": {
      "key": "123456",
      "comments": {
        "one": "111",
        "other": "222"
      }
    }
  },
  "specific": {
    "stuff": "test"
  },
  "title": "The best"
}

Все, что находится под ключом "general", было скопировано как есть.
Что мне нужно, так это:

{
  "general": {
    "level1": {
      "key1": "x-x-x-x-x-x-x-x",
      "key2": "yyyyyyyy",
      "key3": "z-z-z-z-z-z-z-z"
    },
    "onemore": {
      "kkeeyy": "0000000"
    }
  },
  "specific": {
    "stuff": "test"
  },
  "title": {
    "one": "one title",
    "other": "other title"
  }
}

Должны быть скопированы только "key1" и "key3".
Ключи в target JSON не должны быть удалены, а новые ключи не должны создаваться.

Кто-нибудь может помочь?

Ответы [ 3 ]

0 голосов
/ 18 октября 2018

[Примечание: этот ответ отвечает на исходный вопрос относительно исходных данных.OP может иметь в виду пути, а не ключи.]

Нет необходимости вычислять пересечение для достижения достаточно эффективного решения.

Во-первых, давайте предположим следующий вызов jq:

jq -n --argfile source source.json --argfile target target.json -f copy.jq

В файле copy.jq мы можем начать с определения вспомогательной функции:

# emit an array of the distinct terminal keys in the input entity
def keys: [paths | .[-1] | select(type=="string")] | unique;

Для проверки всех путей к конечным элементам $source мыможно использовать tostream:

($target | keys) as $t
| reduce ($source|tostream|select(length==2)) as [$p,$v]
    ($target;
     if $t|index($p[-1]) then setpath($p; $v) else . end)

Альтернативы

Поскольку $ t отсортирован, имеет смысл (по крайней мере, теоретически) использовать bsearch вместо index:

 bsearch($p[-1]) > -1

Также вместо tostream мы могли бы использовать paths(scalars).

Соединение этих альтернатив:

($target | keys) as $t
| reduce ($source|paths(scalars)) as $p
    ($target;
     if $t|bsearch($p[-1]) > -1 
     then setpath($p; $source|getpath($p))
     else . end)

Вывод

{
  "general": {
    "level1": {
      "key1": "x-x-x-x-x-x-x-x",
      "key2": "yyyyyyyy",
      "key3": "z-z-z-z-z-z-z-z"
    },
    "onemore": {
      "kkeeyy": "0000000"
    }
  },
  "specific": {
    "stuff": "test"
  }
}
0 голосов
/ 18 октября 2018

Ниже приведено решение пересмотренного вопроса, который на самом деле касается «путей», а не «ключей».

([$target|paths(scalars)] | unique) as $paths
| reduce ($source|paths(scalars)) as $p
    ($target;
     if $paths | bsearch($p) > -1 
     then setpath($p; $source|getpath($p))
     else . end)

unique вызывается для последующего использования двоичного поиска.

Вызов:

jq -n --argfile source source.json --argfile target target.json -f program.jq
0 голосов
/ 17 октября 2018

Один из подходов, который вы могли бы использовать, - это получить все пути ко всем скалярным значениям для каждого входа и принять установленные пересечения.Затем скопируйте значения из источника в цель из этих путей.

Сначала нам понадобится функция пересечения (которую было удивительно сложно создать):

def set_intersect($other):
    (map({ ($other[] | tojson): true }) | add) as $o
    | reduce (.[] | tojson) as $v ({}; if $o[$v] then .[$v] = true else . end)
    | keys_unsorted
    | map(fromjson);

Затем для обновления:

$ jq --argfile s source.json '
reduce ([paths(scalars)] | set_intersect([$s | paths(scalars)])[]) as $p (.;
    setpath($p; $s | getpath($p))
)
' target.json
...