Преобразовать объект 3D JSON в «поддельный» ассоциативный массив 3D в Bash

У меня есть объект в JSON со структурой, показанной ниже, и я хочу преобразовать его в ассоциативный массив BASH "поддельный 3D" со следующей структурой:

SHIPS[$mmsi:mmsi]=367513050
SHIPS[$mmsi:lat]=42.380329
...etc

Другими словами, я хочу, чтобы значение mmsi в сочетании с ключом каждого объекта стало индексом ассоциативного массива.

Прямо сейчас я делаю следующее. Недостатком является то, что он полагается на то, что поле mmsi будет первым полем каждого объекта JSON, что, вероятно, верно, но также не очень надежно. Это предоставляет 3 объекта: VESSEL_INDEX со всеми mmsis, KEY_INDEX со всеми существующими ключами и VESSELS, к которым можно получить доступ как VESSELS[$mmsi:$key]

Обновлено: для ясности/полноты моей конечной целью является:

  • объявить связанный массив
  • curl в файле JSON из URL-адреса
  • обновить связанный массив (с ключами VESSELS[$mmsi:$key]) с ключами/значениями, предоставленными в объекте JSON, оставив без изменений любые неупомянутые элементы массива.
    declare -A VESSELS
    declare -a VESSEL_INDEX
    declare -a KEY_INDEX

    while read -r keyvalue
    do
        key = "${keyvalue%%=*}"
        value = "${keyvalue#*=}"
        if [[ "$key" == "mmsi" ]]
        then
            mmsi = "$value"
            [[ -n "$mmsi" ]] && VESSEL_INDEX+=("$mmsi")
        fi
        [[ ! " ${KEY_INDEX[*]} " =~ " ${key} " ]] && KEY_INDEX+=("${key}")
        [[ -n "$mmsi" ]] && VESSELS["$mmsi:$key"] = "$value"

    done <<< "$(curl -sL "$AIS_URL/ships_full.json" | jq -r ".ships[]|to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]")"

с этим вводом, свернутым:

{
  "count": 2
  "ships": [
    {
      "mmsi": 367513050,
      "lat": 42.380329,
      "lon": -71.042946,
      "distance": 0.317654,
      "mmsi_type": 1,
      "level": -28.161266,
      "count": 6895,
      "ppm": 4.918982,
      "heading": null,
      "cog": null,
      "speed": 0,
      "to_bow": 10,
      "to_stern": 17,
      "to_starboard": 3,
      "to_port": 5,
      "shiptype": 52,
      "msg_type": 0,
      "country": "US",
      "status": 0,
      "callsign": "WDG2188",
      "shipname": "VINCENT D. TIBBETTS",
      "destination": "BOSTON",
      "last_signal": 0
    },
    {
      "mmsi": 367447520,
      "lat": 42.324032,
      "lon": -70.994347,
      "distance": 3.777312,
      "mmsi_type": 1,
      "level": -37.657475,
      "count": 1103,
      "ppm": -1.157407,
      "heading": 156,
      "cog": 155.5,
      "speed": 28.4,
      "to_bow": 24,
      "to_stern": 20,
      "to_starboard": 9,
      "to_port": 4,
      "shiptype": 40,
      "msg_type": 0,
      "country": "US",
      "status": 0,
      "callsign": "WDF4062",
      "shipname": "SALACIA",
      "destination": "XX XXX>?? ???",
      "last_signal": 0
    },
  ],
  "error": false
}
Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
0
0
58
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Заставьте jq создать оператор declare, используя скобки вокруг ключа и экранируя с помощью @sh:

unset SHIPS
declare -A SHIPS = "($(jq -r '
  .ships[] | .mmsi as $mmsi
  | to_entries[] | @sh "[\("\($mmsi):\(.key)")]=\(.value)"
' ships_full.json ))"

Теперь вы сможете использовать ассоциативный массив $SHIPS в Bash:

echo "${SHIPS["367513050:lat"]}"
42.380329

Tnx. Итак, я столкнулся с проблемой с вашим ответом: он отлично работает, если используется точно так, как вы написали, но он объединяет все выходные данные в один аргумент, если вы разделяете операторы declare и присваивания. (unset SHIPS; declare -A SHIPS; SHIPS = "($(jq -r ...)

kx1t 27.12.2022 15:24

@ kx1t Конечно, это не удается, потому что SHIPS = "(['key']='value')" это не то, как вы объявляете массивы или присваиваете значения элементам в Bash. Пожалуйста, укажите, чего вы хотите достичь. Если вам нужно одно назначение для каждой пары ключ-значение, то есть SHIPS['key']='value', просто соответствующим образом измените выходные данные фильтра jq. Однако имейте в виду, что такое присваивание больше не является оператором declare, поэтому вам нужно будет eval вывести результат, чтобы он был интерпретирован Bash.

pmf 27.12.2022 15:46

В конце концов, я хочу свернуть JSON (например, с помощью < <(curl -sSL https://kx1t.com/ais/ships.json)) и обновить с его помощью существующий массив asoc SHIPS. Любые возвращенные записи будут обновлены из JSON в ${SHIPS[$mmsi:$key]}, а любые другие записи в массиве останутся на месте. В результате я хочу отдельно объявить связанный массив, а потом сделать SHIPS+=( ... )

kx1t 27.12.2022 16:21

@ kx1t Что касается ввода, просто укажите его на стандартном вводе так, как вы предпочитаете, например. curl … | jq … или jq … <(curl …) или тому подобное. Что касается обновления массива, вы в основном хотите прочитать (новый) массив, как описано выше, а затем объединить его с уже существующим.

pmf 27.12.2022 16:30

Основываясь на вашем втором комментарии, у меня есть это, и, похоже, оно работает: unset VESSELS; declare -A VESSELS; eval "$(jq -r '.ships[] | .mmsi as $mmsi | to_entries[] | @sh "VESSELS[\("\($mmsi):\(.key)")]=\(.value)"' < <(curl -sSL http://kx1t.com/ais/ships.json) )"; declare -p VESSELS Я добавлю его как отдельный ответ на свой вопрос и припишу вам за него. Еще раз спасибо!

kx1t 27.12.2022 16:40

Благодаря ответам и подсказкам @pmf теперь у меня есть следующее, которое работает. Выкладываю сюда, чтобы другие могли увидеть:

unset VESSELS
declare -A VESSELS

# ... other code here

eval "$(jq -r '.ships[] | .mmsi as $mmsi | to_entries[] | @sh "VESSELS[\("\($mmsi):\(.key)")]=\(.value)"' < <(curl -sSL http://xxxx.com/ais/ships.json) )"

# check that it worked:
declare -p VESSELS

Другие вопросы по теме