共用方式為


數據處理者中的 jq 運算式是什麼?

重要

已啟用 Azure Arc 的 Azure IoT 操作預覽版目前為預覽狀態。 請勿在生產環境使用此預覽版軟體。

當正式發行可供使用時,您將需要部署新的 Azure IoT 作業安裝,您將無法升級預覽安裝。

請參閱 Microsoft Azure 預覽版增補使用規定,以了解適用於 Azure 功能 (搶鮮版 (Beta)、預覽版,或尚未正式發行的版本) 的法律條款。

jq 運算式提供對資料管線訊息執行計算和操作的強大方式。 本指南示範資料管線中常見計算和處理需求的語言模式和方法。

提示

若要試用本指南中的範例,您可以使用 jq 遊樂場,並將範例輸入和運算式貼到編輯器中。

語言基本概念

如果您不熟悉 jq 作為語言,此語言基本概念區段會提供一些背景資訊。

函式程式設計

jq 語言是功能性程式設計語言。 每項作業都會接受輸入並產生輸出。 多項作業會結合在一起,以執行複雜的邏輯。 例如,假設有下列輸入:

{
  "payload": {
    "temperature": 25
  }
}

以下是一個簡單的 jq 運算式,指定要擷取的路徑:

.payload.temperature

此路徑是接受值做為輸入和輸出另一個值的作業。 在這裡範例中,輸出值為 25

當您在 jq 中使用複雜的鏈結作業時,有一些重要考量:

  • 作業未傳回的任何資料都不再出現在運算式的其餘部分。 此限制式有一些方法,但一般而言,您應該考慮運算式稍後需要哪些資料,並防止它從先前的作業中卸除。
  • 運算式最好視為一系列的資料轉換,而不是一組要執行的計算。 即使是工作分派之類的作業,也只是一個欄位已變更之整體值的轉換。

所有內容都是運算式

在大多數非函式語言中,兩種類型的操作之間存在差異:

  • 運算式產生可在另一個運算式內容中使用的值。
  • 陳述式建立某種形式的副作用,而不是直接操作輸入和輸出。

除了一些例外狀況,jq 中的所有內容都是運算式。 迴圈、if/else 作業,甚至指派都是產生新值的運算式,而不是在系統中建立副作用。 例如,假設有下列輸入:

{
  "temperature": 21,
  "humidity": 65
}

如果我想將 [humidity] 欄位變更為 [63],您可以使用工作指派運算式:

.humidity = 63

雖然此運算式似乎變更輸入物件,但在 jq 中,它會產生具有新值的新物件 humidity

{
  "temperature": 21,
  "humidity": 63
}

此差異似乎很微妙,但表示您可以使用 | 來鏈結此作業的結果,如稍後所述。

使用管道鏈結作業:|

在 jq 中執行計算和資料操作通常需要您將多項作業結合在一起。 您可以藉由在作業之間放置 | 來鏈結作業。 例如,若要計算訊息中資料陣列的長度:

{
  "data": [5, 2, 4, 1]
}

首先,隔離保存陣列的訊息部分:

.data

此運算式只提供陣列:

[5, 2, 4, 1]

然後,使用 length 作業來計算該陣列的長度:

length

此運算式提供您的答案:

4

使用 | 運算子做為步驟之間的分隔符號,因此當做單一 jq 運算式時,計算會變成:

.data | length

如果您嘗試執行複雜的轉換,但此處沒有看到完全符合您問題的範例,您很可能透過將本指南中的多個解決方案鏈結成 | 符號來解決您的問題。

函式輸入和引數

jq 中的其中一個主要作業是呼叫函式。 jq 中的函式有許多形式,而且可以接受不同數目的輸入。 函式輸入有兩種形式:

  • 數據內容 - jq 自動送入函式的數據。 一般而言,作業在最近 | 符號之前所產生的資料。
  • 函式引數 - 您提供的其他運算式和值來設定函式的行為。

許多函式都有零個引數,並使用 jq 提供的資料內容來執行其所有工作。 這length 函式是範例:

["a", "b", "c"] | length

在上一個範例中,length 的輸入是 | 符號左邊建立的陣列。 函式不需要任何其他輸入來計算輸入陣列的長度。 您只要使用其名稱來呼叫具有零引數的函式。 換句話說,請使用 length,而不是 length()

某些函式會將資料內容與單一引數結合,以定義其行為。 例如,map 函式:

[1, 2, 3] | map(. * 2)

在上一個範例中,map 的輸入是在 | 符號左邊建立的數字陣列。 map 函式會針對輸入陣列的每個元素執行運算式。 您將運算式作為引數提供給 map,在本範例中為 . * 2,將陣列中每個輸入的值乘以 2 以輸出陣列 [2, 4, 6]。 您可以使用對應函式來設定您想要的任何內部行為。

某些函式會採用一個以上的引數。 這些函式的運作方式與單一引數函式相同,並使用 ; 符號來分隔引數。 例如,sub 函式:

"Hello World" | sub("World"; "jq!")

在上述範例中,sub 函式會接收 "Hello World" 作為其輸入資料內容,然後接受兩個引數:

  • 字串中要搜尋的規則運算式。
  • 要取代任何相符子字串的字串。 使用 ; 符號分隔引數。 相同的模式適用於具有兩個以上引數的函式。

重要

請務必使用 ; 做為引數分隔符號,而不是 ,

使用物件

有許多方式可從 jq 中擷取、操作及建構物件的資料。 下列各節說明一些最常見的模式:

從物件擷取值

若要擷取索引鍵,您通常會使用路徑運算式。 此作業通常會與其他作業結合,以取得更複雜的結果。

從物件擷取資料很容易。 當您需要從非對象結構擷取許多數據片段時,常見的模式是將非對象結構轉換成物件。 指定下列輸入︰

{
  "payload": {
    "values": {
      "temperature": 45,
      "humidity": 67
    }
  }
}

使用下列運算式來擷取濕度值:

.payload.values.humidity

此運算式會產生下列輸出:

67

變更物件中的索引鍵

若要重新命名或修改物件索引鍵,您可以使用 with_entries 函式。 此函式會採用在物件索引鍵/值組上運作的運算式,並傳回具有運算式結果的新物件。

下列範例示範如何將 temp 欄位重新命名為 temperature,以配合下游結構描述。 指定下列輸入︰

{
  "payload": {
    "temp": 45,
    "humidity": 67
  }
}

使用下列運算式將 temp 欄位重新命名為 temperature

.payload |= with_entries(if .key == "temp" then .key = "temperature" else . end)

在先前的 jq 運算式中:

  • .payload |= <expression> 使用 |=.payload 的值更新為執行 <expression> 的結果。 使用 |= 而不是 =<expression> 的資料內容設為 .payload 而不是 .
  • with_entries(<expression>) 是一起執行數項作業的速記。 它會執行下列作業:
    • 接受物件做為輸入,並將每個索引鍵/值組轉換成具有結構 {"key": <key>, "value": <value>} 的輸入。
    • 針對物件所產生的每個項目執行 <expression>,並將該項目的輸入值取代為執行 <expression> 的結果。
    • 使用 key 作為索引鍵/值組中的索引鍵,使用 value 作為索引鍵的值,將轉換後的輸入集合轉換回物件。
  • if .key == "temp" then .key = "temperature" else . end 會針對輸入的索引鍵執行條件式邏輯。 如果索引鍵是 temp ,則會轉換成 temperature 保留值不變。 如果索引鍵不是 temp,則透過從運算式傳回 . 來保持輸入不變。

下列 JSON 顯示上一個運算式的輸出:

{
  "payload": {
    "temperature": 45,
    "humidity": 67
  }
}

將物件轉換成陣列

雖然物件對於存取資料很有用,但當您想要分割訊息或動態合併資訊時,陣列通常更有用。 使用 to_entries 將物件轉換成陣列。

下列範例示範如何將 payload 欄位轉換成陣列。 指定下列輸入︰

{
  "id": "abc",
  "payload": {
    "temperature": 45,
    "humidity": 67
  }
}

使用下列運算式將承載欄位轉換成陣列:

.payload | to_entries

下列 JSON 是上一個 jq 運算式的輸出:

[
  {
    "key": "temperature",
    "value": 45
  },
  {
    "key": "humidity",
    "value": 67
  }
]

提示

此範例只會擷取陣列,並捨棄訊息中的任何其他資訊。 若要保留整體訊息,但將 .payload 的結構交換至陣列,請改用 .payload |= to_entries

建立物件

您可以使用類似於 JSON 的語法來建構物件,您可以在其中提供靜態和動態資訊的混合。

下列範例示範如何建立具有重新命名欄位和更新結構的新物件,以完全重新建構物件。 指定下列輸入︰

{
  "payload": {
    "Timestamp": 1681926048,
    "Payload": {
      "dtmi:com:prod1:slicer3345:humidity": {
        "SourceTimestamp": 1681926048,
        "Value": 10
      },
      "dtmi:com:prod1:slicer3345:lineStatus": {
        "SourceTimestamp": 1681926048,
        "Value": [1, 5, 2]
      },
      "dtmi:com:prod1:slicer3345:speed": {
        "SourceTimestamp": 1681926048,
        "Value": 85
      },
      "dtmi:com:prod1:slicer3345:temperature": {
        "SourceTimestamp": 1681926048,
        "Value": 46
      }
    },
    "DataSetWriterName": "slicer-3345",
    "SequenceNumber": 461092
  }
}

使用下列 jq 運算式來建立具有新結構的物件:

{
  payload: {
    humidity: .payload.Payload["dtmi:com:prod1:slicer3345:humidity"].Value,
    lineStatus: .payload.Payload["dtmi:com:prod1:slicer3345:lineStatus"].Value,
    temperature: .payload.Payload["dtmi:com:prod1:slicer3345:temperature"].Value
  },
  (.payload.DataSetWriterName): "active"
}

在先前的 jq 運算式中:

  • {payload: {<fields>}} 會建立具有名為 payload 常值欄位的物件,而該欄位本身是包含更多欄位的常值物件。 此方法是建構物件的最基本方式。
  • humidity: .payload.Payload["dtmi:com:prod1:slicer3345:humidity"].Value, 會建立具有動態計算值的靜態索引鍵名稱。 物件建構內所有運算式的資料內容是物件建構運算式的完整輸入,在此案例中為完整訊息。
  • (.payload.DataSetWriterName): "active" 是動態物件索引鍵的範例。 在此範例中,.payload.DataSetWriterName 的值會對應至靜態值。 當您建立物件時,在任何組合中使用靜態和動態索引鍵和值。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": {
    "humidity": 10,
    "lineStatus": [1, 5, 2],
    "temperature": 46
  },
  "slicer-3345": "active"
}

將欄位新增至物件

您可以新增欄位來增加物件,以提供資料的額外內容。 使用工作指派至不存在的欄位。

下列範例示範如何將 averageVelocity 欄位新增至承載。 指定下列輸入︰

{
  "payload": {
    "totalDistance": 421,
    "elapsedTime": 1598
  }
}

使用下列 jq 運算式,將 averageVelocity 欄位新增至承載:

.payload.averageVelocity = (.payload.totalDistance / .payload.elapsedTime)

不同於其他範例所使用的 |= 符號,此範例會使用標準指派 =。 因此,它不會將右側的運算式範圍設定為左邊的欄位。 這種方法是必要的,因此您可以存取承載的其他欄位。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": {
    "totalDistance": 421,
    "elapsedTime": 1598,
    "averageVelocity": 0.2634543178973717
  }
}

有條件地將欄位新增至物件

將條件邏輯與向物件新增欄位的語法結合,可以實現為不存在的欄位新增預設值等情節。

下列範例示範如何將單位新增至沒有單位的任何溫度測量。 預設單位為攝氏。 指定下列輸入︰

{
  "payload": [
    {
      "timestamp": 1689712296407,
      "temperature": 59.2,
      "unit": "fahrenheit"
    },
    {
      "timestamp": 1689712399609,
      "temperature": 52.2
    },
    {
      "timestamp": 1689712400342,
      "temperature": 50.8,
      "unit": "celsius"
    }
  ]
}

使用下列 jq 運算式,將單位新增至沒有任何溫度測量:

.payload |= map(.unit //= "celsius")

在先前的 jq 運算式中:

  • .payload |= <expression> 使用 |=.payload 的值更新為執行 <expression> 的結果。 使用 |= 而不是 =<expression> 的資料內容設為 .payload 而不是 .
  • map(<expression>) 會針對陣列中的每個輸入執行 <expression>,並將輸入值取代為任何 <expression> 所產生的值。
  • .unit //= "celsius" 使用特殊的 //= 指派。 此指派將 (=) 與替代運算子 (//) 結合起來,以便將 .unit 的現有值指派給自身 (如果它不是 falsenull)。 如果 .unit 為 false 或 null,則運算式會視需要將 "celsius" 指派為 .unit 的值,以建立 .unit

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": [
    {
      "timestamp": 1689712296407,
      "temperature": 59.2,
      "unit": "fahrenheit"
    },
    {
      "timestamp": 1689712399609,
      "temperature": 52.2,
      "unit": "celsius"
    },
    {
      "timestamp": 1689712400342,
      "temperature": 50.8,
      "unit": "celsius"
    }
  ]
}

從物件移除欄位

使用 del 函式,從物件中移除不必要的欄位。

下列範例示範如何移除 timestamp 欄位,因為它與其餘計算無關。 指定下列輸入︰

{
  "payload": {
    "timestamp": "2023-07-18T20:57:23.340Z",
    "temperature": 153,
    "pressure": 923,
    "humidity": 24
  }
}

使用下列 jq 運算式會移除 timestamp 欄位:

del(.payload.timestamp)

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": {
    "temperature": 153,
    "pressure": 923,
    "humidity": 24
  }
}

使用陣列 (英文)

陣列是 jq 中反覆運算和訊息分割的核心組建區塊。 下列範例示範如何操作陣列。

從陣列擷取值

陣列比物件更難檢查,因為資料可以位於不同訊息中陣列的不同索引中。 因此,若要從陣列擷取值,您通常必須搜尋陣列中所需的資料。

下列範例示範如何從陣列擷取一些值,以建立一個新的物件,以保存您感興趣的資料。 指定下列輸入︰

{
  "payload": {
    "data": [
      {
        "field": "dtmi:com:prod1:slicer3345:humidity",
        "value": 10
      },
      {
        "field": "dtmi:com:prod1:slicer3345:lineStatus",
        "value": [1, 5, 2]
      },
      {
        "field": "dtmi:com:prod1:slicer3345:speed",
        "value": 85
      },
      {
        "field": "dtmi:com:prod1:slicer3345:temperature",
        "value": 46
      }
    ],
    "timestamp": "2023-07-18T20:57:23.340Z"
  }
}

使用下列 jq 運算式,從陣列擷取 timestamptemperaturehumiditypressure 值,以建立新的物件:

.payload |= {
    timestamp,
    temperature: .data | map(select(.field == "dtmi:com:prod1:slicer3345:temperature"))[0]?.value,
    humidity: .data | map(select(.field == "dtmi:com:prod1:slicer3345:humidity"))[0]?.value,
    pressure: .data | map(select(.field == "dtmi:com:prod1:slicer3345:pressure"))[0]?.value,
}

在先前的 jq 運算式中:

  • .payload |= <expression> 使用 |=.payload 的值更新為執行 <expression> 的結果。 使用 |= 而不是 =<expression> 的資料內容設為 .payload 而不是 .
  • {timestamp, <other-fields>}timestamp: .timestamp 的速記,它會使用原始物件中相同名稱的欄位,將時間戳記當做欄位新增至物件。 <other-fields> 將更多欄位新增至物件。
  • temperature: <expression>, humidity: <expression>, pressure: <expression> 根據三個運算式的結果,在產生的物件中設定溫度、濕度及壓力。
  • .data | <expression> 將值計算範圍設定為承載 data 陣列,並在陣列上執行 <expression>
  • map(<expression>)[0]?.value 會執行數件事:
    • map(<expression>) 會針對陣列中的每個元素執行 <expression> ,以傳回針對每個元素執行該運算式的結果。
    • [0] 擷取所產生陣列的第一個元素。
    • ? 啟用路徑線段的進一步鏈結,即使上述值為 null 也一樣。 當上述值為 null 時,後續路徑也會傳回 null,而不是失敗。
    • .value 會從結果擷取 value 欄位。
  • select(.field == "dtmi:com:prod1:slicer3345:temperature") 會針對輸入執行 select() 內的布林運算式。 如果結果為 true,則會傳遞輸入。 如果結果為 false,則會卸除輸入。 map(select(<expression>)) 是用來篩選陣列中元素的常見組合。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": {
    "timestamp": "2023-07-18T20:57:23.340Z",
    "temperature": 46,
    "humidity": 10,
    "pressure": null
  }
}

變更陣列項目

使用 map() 運算式修改陣列中的項目。 使用這些運算式來修改陣列的每個元素。

下列範例示範如何將陣列中每個項目的時間戳記從 unix 毫秒時間轉換成 RFC3339 字串。 指定下列輸入︰

{
  "payload": [
    {
      "field": "humidity",
      "timestamp": 1689723806615,
      "value": 10
    },
    {
      "field": "lineStatus",
      "timestamp": 1689723849747,
      "value": [1, 5, 2]
    },
    {
      "field": "speed",
      "timestamp": 1689723868830,
      "value": 85
    },
    {
      "field": "temperature",
      "timestamp": 1689723880530,
      "value": 46
    }
  ]
}

使用下列 jq 運算式,將陣列中每個項目的時間戳記從 unix 毫秒時間轉換成 RFC3339 字串:

.payload |= map(.timestamp |= (. / 1000 | strftime("%Y-%m-%dT%H:%M:%SZ")))

在先前的 jq 運算式中:

  • .payload |= <expression> 使用 |=.payload 的值更新為執行 <expression> 的結果。 使用 |= 而不是 =<expression> 的資料內容設為 .payload 而不是 .
  • map(<expression>) 會針對陣列中的每個元素執行 <expression>,並將每個元素取代為執行中 <expression> 的輸出。
  • .timestamp |= <expression> 根據執行 <expression> 將時間戳記設定為新值,其中 <expression> 的資料內容是 .timestamp 的值。
  • (. / 1000 | strftime("%Y-%m-%dT%H:%M:%SZ")) 將毫秒時間轉換為秒數,並使用時間字串格式器來產生 ISO 8601 時間戳記。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": [
    {
      "field": "humidity",
      "timestamp": "2023-07-18T23:43:26Z",
      "value": 10
    },
    {
      "field": "lineStatus",
      "timestamp": "2023-07-18T23:44:09Z",
      "value": [1, 5, 2]
    },
    {
      "field": "speed",
      "timestamp": "2023-07-18T23:44:28Z",
      "value": 85
    },
    {
      "field": "temperature",
      "timestamp": "2023-07-18T23:44:40Z",
      "value": 46
    }
  ]
}

將陣列轉換成物件

若要將陣列重新建構為物件,以便更容易存取或符合所需的結構描述,請使用 from_entries。 指定下列輸入︰

{
  "payload": [
    {
      "field": "humidity",
      "timestamp": 1689723806615,
      "value": 10
    },
    {
      "field": "lineStatus",
      "timestamp": 1689723849747,
      "value": [1, 5, 2]
    },
    {
      "field": "speed",
      "timestamp": 1689723868830,
      "value": 85
    },
    {
      "field": "temperature",
      "timestamp": 1689723880530,
      "value": 46
    }
  ]
}

使用下列 jq 運算式將陣列轉換成物件:

.payload |= (
    map({key: .field, value: {timestamp, value}})
    | from_entries
)

在先前的 jq 運算式中:

  • .payload |= <expression> 使用 |=.payload 的值更新為執行 <expression> 的結果。 使用 |= 而不是 =<expression> 的資料內容設為 .payload 而不是 .
  • map({key: <expression>, value: <expression>}) 會將陣列的每個元素轉換成表單 {"key": <data>, "value": <data>}的物件,這是結構 from_entries 需求。
  • {key: .field, value: {timestamp, value}} 會從陣列項目建立物件、將 field 對應至索引鍵,以及建立一個值,這個值是保存 timestampvalue 的物件。 {timestamp: .timestamp, value: .value} 的速記 {timestamp, value}
  • <expression> | from_entries 將陣列值 <expression> 轉換成物件,將每個陣列項目的 key 欄位對應至物件索引鍵,並將每個陣列項目的 value 欄位對應至該索引鍵的值。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": {
    "humidity": {
      "timestamp": 1689723806615,
      "value": 10
    },
    "lineStatus": {
      "timestamp": 1689723849747,
      "value": [1, 5, 2]
    },
    "speed": {
      "timestamp": 1689723868830,
      "value": 85
    },
    "temperature": {
      "timestamp": 1689723880530,
      "value": 46
    }
  }
}

建立陣列

建立陣列常值類似於建立物件常值。 陣列常值的 jq 語法類似 JSON 和 JavaScript。

下列範例示範如何將某些值擷取至簡單的陣列,以供稍後處理。

指定下列輸入︰

{
  "payload": {
    "temperature": 14,
    "humidity": 56,
    "pressure": 910
  }
}

使用下列 jq 運算式會從 temperaturehumiditypressure 欄位的值建立陣列:

.payload |= ([.temperature, .humidity, .pressure])

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": [14, 56, 910]
}

將項目新增至陣列

您可以使用具有陣列及其新項目的 + 運算子,將項目新增至陣列的開頭或結尾。 += 運算子會透過在結尾使用新項目自動更新陣列,以簡化此作業。 指定下列輸入︰

{
  "payload": {
    "Timestamp": 1681926048,
    "Payload": {
      "dtmi:com:prod1:slicer3345:humidity": {
        "SourceTimestamp": 1681926048,
        "Value": 10
      },
      "dtmi:com:prod1:slicer3345:lineStatus": {
        "SourceTimestamp": 1681926048,
        "Value": [1, 5, 2]
      },
      "dtmi:com:prod1:slicer3345:speed": {
        "SourceTimestamp": 1681926048,
        "Value": 85
      },
      "dtmi:com:prod1:slicer3345:temperature": {
        "SourceTimestamp": 1681926048,
        "Value": 46
      }
    },
    "DataSetWriterName": "slicer-3345",
    "SequenceNumber": 461092
  }
}

使用下列 jq 運算式,將值 1241 新增至 lineStatus 值陣列的結尾:

.payload.Payload["dtmi:com:prod1:slicer3345:lineStatus"].Value += [12, 41]

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": {
    "Timestamp": 1681926048,
    "Payload": {
      "dtmi:com:prod1:slicer3345:humidity": {
        "SourceTimestamp": 1681926048,
        "Value": 10
      },
      "dtmi:com:prod1:slicer3345:lineStatus": {
        "SourceTimestamp": 1681926048,
        "Value": [1, 5, 2, 12, 41]
      },
      "dtmi:com:prod1:slicer3345:speed": {
        "SourceTimestamp": 1681926048,
        "Value": 85
      },
      "dtmi:com:prod1:slicer3345:temperature": {
        "SourceTimestamp": 1681926048,
        "Value": 46
      }
    },
    "DataSetWriterName": "slicer-3345",
    "SequenceNumber": 461092
  }
}

從陣列中移除項目

使用 del 函式,以與物件相同的方式,從陣列中移除項目。 指定下列輸入︰

{
  "payload": {
    "Timestamp": 1681926048,
    "Payload": {
      "dtmi:com:prod1:slicer3345:humidity": {
        "SourceTimestamp": 1681926048,
        "Value": 10
      },
      "dtmi:com:prod1:slicer3345:lineStatus": {
        "SourceTimestamp": 1681926048,
        "Value": [1, 5, 2]
      },
      "dtmi:com:prod1:slicer3345:speed": {
        "SourceTimestamp": 1681926048,
        "Value": 85
      },
      "dtmi:com:prod1:slicer3345:temperature": {
        "SourceTimestamp": 1681926048,
        "Value": 46
      }
    },
    "DataSetWriterName": "slicer-3345",
    "SequenceNumber": 461092
  }
}

使用下列 jq 運算式,從 lineStatus 值陣列中移除第二個項目:

del(.payload.Payload["dtmi:com:prod1:slicer3345:lineStatus"].Value[1])

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": {
    "Timestamp": 1681926048,
    "Payload": {
      "dtmi:com:prod1:slicer3345:humidity": {
        "SourceTimestamp": 1681926048,
        "Value": 10
      },
      "dtmi:com:prod1:slicer3345:lineStatus": {
        "SourceTimestamp": 1681926048,
        "Value": [1, 2]
      },
      "dtmi:com:prod1:slicer3345:speed": {
        "SourceTimestamp": 1681926048,
        "Value": 85
      },
      "dtmi:com:prod1:slicer3345:temperature": {
        "SourceTimestamp": 1681926048,
        "Value": 46
      }
    },
    "DataSetWriterName": "slicer-3345",
    "SequenceNumber": 461092
  }
}

移除重複的陣列項目

如果陣列元素重疊,您可以移除重複的項目。 在大部分的程式設計語言中,您可以使用側邊查閱變數來移除重複項目。 在 jq 中,最好的方法是將資料組織成應該處理的方式,然後執行任何作業,再將它轉換成所需的格式。

下列範例示範如何擷取內含一些值的訊息,然後加以篩選,讓每個值只有最新的讀取。 指定下列輸入︰

{
  "payload": [
    {
      "name": "temperature",
      "value": 12,
      "timestamp": 1689727870701
    },
    {
      "name": "humidity",
      "value": 51,
      "timestamp": 1689727944440
    },
    {
      "name": "temperature",
      "value": 15,
      "timestamp": 1689727994085
    },
    {
      "name": "humidity",
      "value": 25,
      "timestamp": 1689727914558
    },
    {
      "name": "temperature",
      "value": 31,
      "timestamp": 1689727987072
    }
  ]
}

使用下列 jq 運算式來篩選輸入,讓每個值只有最新的讀取:

.payload |= (group_by(.name) | map(sort_by(.timestamp)[-1]))

提示

如果您不在意擷取每個名稱的最新值,則可以將運算式簡化為 .payload |= unique_by(.name)

在先前的 jq 運算式中:

  • .payload |= <expression> 使用 |=.payload 的值更新為執行 <expression> 的結果。 使用 |= 而不是 =<expression> 的資料內容設為 .payload 而不是 .
  • group_by(.name) 指定陣列做為輸入,會根據每個元素中的 值 .name ,將專案放入子陣列中。 每個子陣列都包含原始數位中具有相同值 .name的所有專案。
  • map(<expression>) 會採用 所產生的 group_by 陣列陣列,並針對每個子陣列執行 <expression>
  • sort_by(.timestamp)[-1] 從每個子陣列擷取您關心的專案:
    • sort_by(.timestamp) 藉由為目前子陣列增加其 .timestamp 欄位的值來排序專案。
    • [-1] 會從已排序的子陣列擷取最後一個專案,這是每個名稱最近時間的專案。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": [
    {
      "name": "humidity",
      "value": 51,
      "timestamp": 1689727944440
    },
    {
      "name": "temperature",
      "value": 15,
      "timestamp": 1689727994085
    }
  ]
}

計算陣列元素之間的值

您可以結合陣列元素的值來計算值,例如整個元素的平均值。

這個範例示範如何藉由擷取共用相同名稱之項目的最高時間戳記和平均值來減少陣列。 指定下列輸入︰

{
  "payload": [
    {
      "name": "temperature",
      "value": 12,
      "timestamp": 1689727870701
    },
    {
      "name": "humidity",
      "value": 51,
      "timestamp": 1689727944440
    },
    {
      "name": "temperature",
      "value": 15,
      "timestamp": 1689727994085
    },
    {
      "name": "humidity",
      "value": 25,
      "timestamp": 1689727914558
    },
    {
      "name": "temperature",
      "value": 31,
      "timestamp": 1689727987072
    }
  ]
}

使用下列 jq 運算式來擷取共用相同名稱之項目的最高時間戳記和平均值:

.payload |= (group_by(.name) | map(
  {
    name: .[0].name,
    value: map(.value) | (add / length),
    timestamp: map(.timestamp) | max
  }
))

在先前的 jq 運算式中:

  • .payload |= <expression> 使用 |=.payload 的值更新為執行 <expression> 的結果。 使用 |= 而不是 =<expression> 的資料內容設為 .payload 而不是 .
  • group_by(.name) 會採用陣列做為輸入,根據每個元素中的 值 .name ,將元素放入子陣列中。 每個子陣列都包含原始數位中具有相同值 .name的所有專案。
  • map(<expression>) 會採用 所產生的 group_by 陣列陣列,並針對每個子陣列執行 <expression>
  • {name: <expression>, value: <expression>, timestamp: <expression>}使用、 valuetimestamp 欄位,從輸入子陣列name建構物件。 每個 <expression> 都會產生相關聯索引鍵所需的值。
  • .[0].name 從子陣列擷取第一個專案,並從中擷取 name 字段。 子陣列中的所有專案都有相同的名稱,因此您只需要擷取第一個元素。
  • map(.value) | (add / length) 計算每個子陣列的平均值 value
    • map(.value) 將子陣轉換成每個專案中欄位的 value 陣列,在此情況下會傳回數位數位數位數位。
    • add 是內建 jq 函式,用來計算數字陣列的總和。
    • length 是內建 jq 函式,用來計算數字陣列的總和。
    • add / length 將總和除以計數以確定平均值。
  • map(.timestamp) | max 會尋找每個子陣列的最大值 timestamp
    • map(.timestamp) 將子陣轉換成每個專案中欄位的 timestamp 陣列,在此情況下會傳回數位數位數位數位。
    • max 是內建 jq 函式,可尋找陣列中的最大值。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": [
    {
      "name": "humidity",
      "value": 38,
      "timestamp": 1689727944440
    },
    {
      "name": "temperature",
      "value": 19.333333333333332,
      "timestamp": 1689727994085
    }
  ]
}

處理字串

jq 提供數個用來操作和建構字串的公用程式。 下列範例顯示一些常見的使用案例。

分割字串

如果字串包含以一般字元分隔的多個資訊片段,您可以使用 split() 函式來擷取個別片段。

下列範例示範如何分割主題字串,並傳回主題的特定區段。 當您使用分割區索引鍵運算式時,這項技術通常很有用。 指定下列輸入︰

{
  "systemProperties": {
    "timestamp": "2023-01-11T10:02:07Z"
  },
  "qos": 1,
  "topic": "assets/slicer-3345/tags/rpm",
  "properties": {
    "contentType": "application/json"
  },
  "payload": {
    "Timestamp": 1681926048,
    "Value": 142
  }
}

使用下列 jq 運算式來分割主題字串,使用 / 做為分隔符號,並傳回主題的特定區段:

.topic | split("/")[1]

在先前的 jq 運算式中:

  • .topic | <expression> 會從根物件中選取 topic 索引鍵,並針對它所包含的資料執行 <expression>
  • split("/") 在字串中找到 / 字元時,將主題字串分割成陣列。 在這裡情況下,會產生 ["assets", "slicer-3345", "tags", "rpm"]
  • [1] 從上一個步驟中擷取陣列索引 1 處的元素,在本例中為 slicer-3345

下列 JSON 顯示上一個 jq 運算式的輸出:

"slicer-3345"

以動態方式建構字串

jq 可讓您使用字串範本搭配字串內的語法 \(<expression>) 來建構字串。 使用這些範本以動態方式組建字串。

下列範例示範如何使用字串範本,將前置詞新增至物件中的每個索引鍵。 指定下列輸入︰

{
  "temperature": 123,
  "humidity": 24,
  "pressure": 1021
}

使用下列 jq 運算式,將前置詞新增至 物件中的每個索引鍵:

with_entries(.key |= "current-\(.)")

在先前的 jq 運算式中:

  • with_entries(<expression>) 會將物件轉換成具有結構 {key: <key>, value: <value>} 的索引鍵/值組陣列,針對每個索引鍵/值組執行 <expression>,並將配對轉換回物件。
  • .key |= <expression> 會將索引鍵/值組物件中的 .key 值更新為 <expression> 的結果。 使用 |= 而不是 = 會將 <expression> 的資料內容設定為 .key 的值,而不是完整的索引鍵/值組物件。
  • "current-\(.)" 會產生開頭為 "current-" 的字串,然後將目前資料內容的值插入 .,在此案例中為索引鍵的值。 字串內的 \(<expression>) 語法表示您想要將字串的部分取代為執行 <expression> 的結果。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "current-temperature": 123,
  "current-humidity": 24,
  "current-pressure": 1021
}

使用規則運算式

jq 支援標準規則運算式。 您可以使用規則運算式來擷取、取代及檢查字串內的模式。 jq 的常見規則運算式函式包括 test()match()split()capture()sub()gsub()

使用規則運算式擷取值

如果您無法使用字串分割來擷取字串中的值,請嘗試使用正則表示式來擷取您需要的值。

下列範例示範如何測試規則運算式,然後將物件索引鍵取代為不同的格式,以正規化物件索引鍵。 指定下列輸入︰

{
  "payload": {
    "Timestamp": 1681926048,
    "Payload": {
      "dtmi:com:prod1:slicer3345:humidity": {
        "SourceTimestamp": 1681926048,
        "Value": 10
      },
      "dtmi:com:prod1:slicer3345:speed": {
        "SourceTimestamp": 1681926048,
        "Value": 85
      },
      "temperature": {
        "SourceTimestamp": 1681926048,
        "Value": 46
      }
    },
    "DataSetWriterName": "slicer-3345",
    "SequenceNumber": 461092
  }
}

使用下列 jq 運算式將物件索引鍵正規化:

.payload.Payload |= with_entries(
    .key |= if test("^dtmi:.*:(?<tag>[^:]+)$") then
        capture("^dtmi:.*:(?<tag>[^:]+)$").tag
    else
        .
    end
)

在先前的 jq 運算式中:

  • .payload |= <expression> 使用 |=.payload 的值更新為執行 <expression> 的結果。 使用 |= 而不是 =<expression> 的資料內容設為 .payload 而不是 .
  • with_entries(<expression>) 會將物件轉換成具有結構 {key: <key>, value: <value>} 的索引鍵/值組陣列,針對每個索引鍵/值組執行 <expression>,並將配對轉換回物件。
  • .key |= <expression> 會將索引鍵/值組物件中的 .key 值更新為 <expression> 的結果。 使用 |= 而不是 = 會將 <expression> 的資料內容設定為 .key 的值,而不是完整的索引鍵/值組物件。
  • if test("^dtmi:.*:(?<tag>[^:]+)$") then capture("^dtmi:.*:(?<tag>[^:]+)$").tag else . end 會根據規則運算式檢查並更新索引鍵:
    • test("^dtmi:.*:(?<tag>[^:]+)$") 根據規則運算式 ^dtmi:.*:(?<tag>[^:]+)$ 檢查輸入資料內容 (本案例中的索引鍵)。 如果規則運算式相符,則會傳回 true。 如果沒有,則會傳回 false。
    • capture("^dtmi:.*:(?<tag>[^:]+)$").tag 針對輸入資料內容 (本例中的索引鍵) 執行規則運算式 ^dtmi:.*:(?<tag>[^:]+)$,並將規則運算式中的任何擷取的群組 (由 (?<tag>...) 指示) 放置在物件中作為輸出。 然後,運算式會從該物件擷取 .tag,以傳回規則運算式擷取的資訊。
    • .else 分支中,運算式會透過未變更的方式傳遞資料。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": {
    "Timestamp": 1681926048,
    "Payload": {
      "humidity": {
        "SourceTimestamp": 1681926048,
        "Value": 10
      },
      "speed": {
        "SourceTimestamp": 1681926048,
        "Value": 85
      },
      "temperature": {
        "SourceTimestamp": 1681926048,
        "Value": 46
      }
    },
    "DataSetWriterName": "slicer-3345",
    "SequenceNumber": 461092
  }
}

將訊息分割

jq 語言的實用功能是能夠從單一輸入產生多個輸出。 這項功能可讓您將訊息分割成多個個別的訊息,讓管線進行處理。 這項技術的索引鍵是 .[],它會將陣列分割成不同的值。 下列範例顯示一些使用此語法的案例。

動態輸出數目

一般而言,當您想要將訊息分割成多個輸出時,您想要的輸出數目取決於訊息的結構。 [] 語法可讓您執行這種類型的分割。

例如,您有一個帶有標籤清單的訊息,您希望將其放入單獨的訊息中。 指定下列輸入︰

{
  "systemProperties": {
    "partitionKey": "slicer-3345",
    "partitionId": 5,
    "timestamp": "2023-01-11T10:02:07Z"
  },
  "qos": 1,
  "topic": "assets/slicer-3345",
  "properties": {
    "responseTopic": "assets/slicer-3345/output",
    "contentType": "application/json"
  },
  "payload": {
    "Timestamp": 1681926048,
    "Payload": {
      "dtmi:com:prod1:slicer3345:humidity": {
        "sourceTimestamp": 1681926048,
        "value": 10
      },
      "dtmi:com:prod1:slicer3345:lineStatus": {
        "sourceTimestamp": 1681926048,
        "value": [1, 5, 2]
      },
      "dtmi:com:prod1:slicer3345:speed": {
        "sourceTimestamp": 1681926048,
        "value": 85
      },
      "dtmi:com:prod1:slicer3345:temperature": {
        "sourceTimestamp": 1681926048,
        "value": 46
      }
    },
    "DataSetWriterName": "slicer-3345",
    "SequenceNumber": 461092
  }
}

使用下列 jq 運算式將訊息分割成多個訊息:

.payload.Payload = (.payload.Payload | to_entries[])
| .payload |= {
  DataSetWriterName,
  SequenceNumber,
  Tag: .Payload.key,
  Value: .Payload.value.value,
  Timestamp: .Payload.value.sourceTimestamp
}

在先前的 jq 運算式中:

  • .payload.Payload = (.payload.Payload | to_entries[]) 將訊息分割成數個訊息:
    • .payload.Payload = <expression> 將執行 <expression> 的結果指派給 .payload.Payload。 一般而言,在此案例中,您會使用 |=,將 <expression> 的內容限定為 .payload.Payload,但 |= 不支援將訊息分開分割,因此請改用 =
    • (.payload.Payload | <expression>) 將指派運算式的右側範圍縮小到 .payload.Payload ,讓 <expression> 針對訊息的正確部分運作。
    • to_entries[] 是兩個作業,而且是 to_entries | .[] 的速記:
      • to_entries 會將物件轉換成索引鍵/值組的陣列,並搭配結構描述 {"key": <key>, "value": <value>}。 這項資訊是您想要分隔成不同訊息的內容。
      • [] 會執行訊息分割。 陣列中的每個項目都會成為 jq 中的個別值。 當指派 .payload.Payload 發生時,每個個別的值都會產生整體訊息的複本,且 .payload.Payload 設定為指派右側所產生的對應值。
  • .payload |= <expression> 會以執行 <expression> 的結果取代 .payload 的值。 此時,查詢會處理值的資料流,而不是前一個作業中分割的結果的單一值。 因此,會針對先前作業產生的每個訊息執行一次指派,而不是只針對整體執行一次。
  • {DataSetWriterName, SequenceNumber, ...} 建構新的物件,其值為 .payloadDataSetWriterNameSequenceNumber 不變,因此您可以使用速記語法,而不是撰寫 DataSetWriterName: .DataSetWriterNameSequenceNumber: .SequenceNumber
  • Tag: .Payload.key, 從內部 Payload 擷取原始物件索引鍵,並將其向上層級擷取至父代物件。 查詢稍早的 to_entries 作業會建立 [key] 欄位。
  • Value: .Payload.value.valueTimestamp: .Payload.value.sourceTimestamp 從內部承載執行類似的資料擷取。 這次來自原始索引鍵/值組的值。 結果是一般承載物件,可用於進一步處理。

下列 JSON 顯示上一個 jq 運算式的輸出。 每個輸出都會成為管線中後續處理階段的獨立訊息:

{
  "systemProperties": {
    "partitionKey": "slicer-3345",
    "partitionId": 5,
    "timestamp": "2023-01-11T10:02:07Z"
  },
  "qos": 1,
  "topic": "assets/slicer-3345",
  "properties": {
    "responseTopic": "assets/slicer-3345/output",
    "contentType": "application/json"
  },
  "payload": {
    "DataSetWriterName": "slicer-3345",
    "SequenceNumber": 461092,
    "Tag": "dtmi:com:prod1:slicer3345:humidity",
    "Value": 10,
    "Timestamp": 1681926048
  }
}
{
  "systemProperties": {
    "partitionKey": "slicer-3345",
    "partitionId": 5,
    "timestamp": "2023-01-11T10:02:07Z"
  },
  "qos": 1,
  "topic": "assets/slicer-3345",
  "properties": {
    "responseTopic": "assets/slicer-3345/output",
    "contentType": "application/json"
  },
  "payload": {
    "DataSetWriterName": "slicer-3345",
    "SequenceNumber": 461092,
    "Tag": "dtmi:com:prod1:slicer3345:lineStatus",
    "Value": [1, 5, 2],
    "Timestamp": 1681926048
  }
}
{
  "systemProperties": {
    "partitionKey": "slicer-3345",
    "partitionId": 5,
    "timestamp": "2023-01-11T10:02:07Z"
  },
  "qos": 1,
  "topic": "assets/slicer-3345",
  "properties": {
    "responseTopic": "assets/slicer-3345/output",
    "contentType": "application/json"
  },
  "payload": {
    "DataSetWriterName": "slicer-3345",
    "SequenceNumber": 461092,
    "Tag": "dtmi:com:prod1:slicer3345:speed",
    "Value": 85,
    "Timestamp": 1681926048
  }
}
{
  "systemProperties": {
    "partitionKey": "slicer-3345",
    "partitionId": 5,
    "timestamp": "2023-01-11T10:02:07Z"
  },
  "qos": 1,
  "topic": "assets/slicer-3345",
  "properties": {
    "responseTopic": "assets/slicer-3345/output",
    "contentType": "application/json"
  },
  "payload": {
    "DataSetWriterName": "slicer-3345",
    "SequenceNumber": 461092,
    "Tag": "dtmi:com:prod1:slicer3345:temperature",
    "Value": 46,
    "Timestamp": 1681926048
  }
}

固定輸出數目

若要根據訊息的結構將訊息分割成固定數目的輸出,而不是動態數目的輸出,請使用 , 運算子,而不是 []

下列範例示範如何根據現有的功能變數名稱,將資料分割成兩個訊息。 指定下列輸入︰

{
  "topic": "test/topic",
  "payload": {
    "minTemperature": 12,
    "maxTemperature": 23,
    "minHumidity": 52,
    "maxHumidity": 92
  }
}

使用下列 jq 運算式將訊息分割成兩個訊息:

.payload = (
  {
    field: "temperature",
    minimum: .payload.minTemperature,
    maximum: .payload.maxTemperature
  },
  {
    field: "humidity",
    minimum: .payload.minHumidity,
    maximum: .payload.maxHumidity
  }
)

在先前的 jq 運算式中:

  • .payload = ({<fields>},{<fields>}) 將兩個物件常值指派給訊息中的 .payload。 逗號分隔物件會產生兩個不同的值,並指派給 .payload,這會導致整個訊息分割成兩個訊息。 每個新訊息 .payload 設定為其中一個值。
  • {field: "temperature", minimum: .payload.minTemperature, maximum: .payload.maxTemperature} 是常值物件建構函式,會以常值字串和其他從物件擷取的資料填入物件的欄位。

下列 JSON 顯示上一個 jq 運算式的輸出。 每個輸出都會成為獨立訊息,以供進一步處理階段使用:

{
  "topic": "test/topic",
  "payload": {
    "field": "temperature",
    "minimum": 12,
    "maximum": 23
  }
}
{
  "topic": "test/topic",
  "payload": {
    "field": "humidity",
    "minimum": 52,
    "maximum": 92
  }
}

數學運算

jq 支援常見的數學運算。 某些作業是運算子,例如 +-。 其他作業是函式,例如 sinexp

算術

jq 支援五個常見的算術運算:加法(+)、減法(-)、乘法(*)、除法(/)和模數(%)。 與 jq 的許多功能不同,這些作業是 infix 作業,可讓您在沒有 | 分隔符號的單一運算式中撰寫完整的數學運算式。

下列範例示範如何將溫度從華氏轉換為攝氏,並從 unix 毫秒時間戳記擷取目前的秒數。 指定下列輸入︰

{
  "payload": {
    "temperatureF": 94.2,
    "timestamp": 1689766750628
  }
}

使用下列 jq 運算式將溫度從華氏轉換為攝氏,並從 unix 毫秒時間戳記擷取目前的秒數:

.payload.temperatureC = (5/9) * (.payload.temperatureF - 32)
| .payload.seconds = (.payload.timestamp / 1000) % 60

在先前的 jq 運算式中:

  • .payload.temperatureC = (5/9) * (.payload.temperatureF - 32) 會在承載集中建立新的 temperatureC 欄位,以 temperatureF 從華氏轉換為攝氏。
  • .payload.seconds = (.payload.timestamp / 1000) % 60 需要 unix 毫秒的時間,並將它轉換成秒,然後使用模數計算擷取目前分鐘中的秒數。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": {
    "temperatureF": 94.2,
    "timestamp": 1689766750628,
    "temperatureC": 34.55555555555556,
    "seconds": 10
  }
}

數學函式

jq 包含數個執行數學運算的函式。 您可以在 jq 手冊中找到完整清單。

以下範例向您展示如何根據質量和速度欄位計算動能。 指定下列輸入︰

{
  "userProperties": [
    { "key": "mass", "value": 512.1 },
    { "key": "productType", "value": "projectile" }
  ],
  "payload": {
    "velocity": 97.2
  }
}

使用下列 jq 運算式,從質量與速度欄位計算動能:

.payload.energy = (0.5 * (.userProperties | from_entries).mass * pow(.payload.velocity; 2) | round)

在先前的 jq 運算式中:

  • .payload.energy = <expression> 會在承載中建立新的 energy 欄位,這是執行 <expression> 的結果。
  • (0.5 * (.userProperties | from_entries).mass * pow(.payload.velocity; 2) | round) 是能量的公式:
    • (.userProperties | from_entries).massuserProperties 清單中擷取 mass 項目。 資料已設定為具有 keyvalue 的物件,因此 from_entries 可以直接將它轉換成物件。 運算式會從產生的物件擷取 mass 索引鍵,並傳回其值。
    • pow(.payload.velocity; 2) 從承載中擷取速度,並透過將其提高到 2 次方來平方。
    • <expression> | round 將結果四捨五入為最接近的整數,以避免在結果中產生誤導性的高精確度。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "userProperties": [
    { "key": "mass", "value": 512.1 },
    { "key": "productType", "value": "projectile" }
  ],
  "payload": {
    "velocity": 97.2,
    "energy": 2419119
  }
}

布林邏輯

資料處理管線通常會使用 jq 來篩選訊息。 篩選通常會使用布林運算式和運算子。 此外,布林邏輯有助於在轉換中執行控制流程,以及更進階的篩選使用案例。

下列範例顯示 jq 中布爾表示式中使用的一些最常見功能:

基本布林值和條件運算子

jq 提供 andornot的基本布林邏輯運算子。 andor 運算子是 infix 運算子。 not 是您叫用做篩選的函式,例如,<expression> | not

jq 具有條件運算符 ><==!=>=<=。 這些運算子是 infix 運算子。

下列範例示範如何使用條件式執行一些基本布林邏輯。 指定下列輸入︰

{
  "payload": {
    "temperature": 50,
    "humidity": 92,
    "site": "Redmond"
  }
}

使用下列 jq 運算式來檢查其中一項:

  • 溫度在上限上介於 30 度到 60 度之間。
  • 濕度小於 80,網站為 Redmond。
.payload
| ((.temperature > 30 and .temperature <= 60) or .humidity < 80) and .site == "Redmond"
| not

在先前的 jq 運算式中:

  • .payload | <expression><expression> 的範圍限定為 .payload 的內容。 此語法會使運算式的其餘部分沒有那麼詳細。
  • 如果溫度介於 30 度到 60 度之間,或濕度小於 80 度,則 ((.temperature > 30 and .temperature <= 60) or .humidity < 80) and .site == "Redmond" 會傳回 true,則只有在網站也是 Redmond 時才會傳回 true。
  • <expression> | not 會取得上述運算式的結果,並將邏輯 NOT 套用至該運算式,在此範例中會將結果從 true 反轉為 false

下列 JSON 顯示上一個 jq 運算式的輸出:

false

檢查物件索引鍵是否存在

您可以建立篩選來檢查訊息的結構,而不是其內容。 例如,您可以檢查物件中是否有特定索引鍵。 若要執行這項檢查,請使用 has 函式或針對 null 的檢查。 下列範例顯示這兩種方法。 指定下列輸入︰

{
  "payload": {
    "temperature": 51,
    "humidity": 41,
    "site": null
  }
}

使用以下 jq 運算式檢查承載是否具有 temperature 欄位、site 欄位是否不為空以及其他檢查:

.payload | {
    hasTemperature: has("temperature"),
    temperatureNotNull: (.temperature != null),
    hasSite: has("site"),
    siteNotNull: (.site != null),
    hasMissing: has("missing"),
    missingNotNull: (.missing != null),
    hasNested: (has("nested") and (.nested | has("inner"))),
    nestedNotNull: (.nested?.inner != null)
}

在先前的 jq 運算式中:

  • .payload | <expression><expression> 的資料內容範圍限定為 .payload 的值,以使 <expression> 不那麼冗長。
  • hasTemperature: has("temperature"), 這個和其他類似的運算式示範 has 函式如何與輸入物件運作。 只有在索引鍵存在時,函式才會傳回 true。 儘管 site 的值為 null ,但 hasSite 為 true。
  • temperatureNotNull: (.temperature != null), 這個和其他類似的運算式示範了 != null 檢查如何執行與 has 類似的檢查。 如果使用 .<key> 語法存取,則物件中不存在的索引鍵為 null,或索引鍵存在但值為 null。 即使一個索引鍵存在而另一個索引鍵不存在,siteNotNullmissingNotNull 都是 false。
  • hasNested: (has("nested") and (.nested | has("inner"))) 使用 對巢狀物件 has執行檢查,其中父物件可能不存在。 結果是每個層級的一連串檢查,以避免發生錯誤。
  • nestedNotNull: (.nested?.inner != null) 使用 != null 對巢狀物件執行相同的檢查,並在 ? 可能不存在的欄位上啟用路徑鏈結。 此方法會針對可能或可能不存在的深層巢狀鏈結產生更簡潔的語法,但無法區分 null 索引鍵值與不存在的索引鍵值。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "hasTemperature": true,
  "temperatureNotNull": true,
  "hasSite": true,
  "siteNotNull": false,
  "hasMissing": false,
  "missingNotNull": false,
  "hasNested": false,
  "nestedNotNull": false
}

檢查陣列項目是否存在

使用 any 函式來檢查陣列中是否有項目。 指定下列輸入︰

{
  "userProperties": [
    { "key": "mass", "value": 512.1 },
    { "key": "productType", "value": "projectile" }
  ],
  "payload": {
    "velocity": 97.2,
    "energy": 2419119
  }
}

使用下列 jq 運算式來檢查 userProperties 陣列是否有具有 mass 索引鍵的項目,且沒有具有 missing 索引鍵的項目:

.userProperties | any(.key == "mass") and (any(.key == "missing") | not)

在先前的 jq 運算式中:

  • .userProperties | <expression><expression> 的資料內容範圍限定為 userProperties 的值,以使其餘的 <expression> 不那麼冗長。
  • any(.key == "mass") 會針對 userProperties 陣列的每個元素執行 .key == "mass" 運算式,如果運算式評估為 true,則至少會傳回陣列的一個元素。
  • (any(.key == "missing") | not) 會針對 userProperties 陣列的每個元素執行 .key == "missing",如果有任何元素評估為 true,則會傳回 true,然後使用 | not 否定整體結果。

下列 JSON 顯示上一個 jq 運算式的輸出:

true

控制流程

jq 中的控制流程與大多數語言不同,因為大部分的控制流程都是直接以資料驅動。 仍然支援具有傳統功能程式設計語意的 if/else 運算式,但您可以使用 mapreduce 函式的組合來達成大部分的迴圈結構。

下列範例顯示 jq 中的一些常見控制流程情節。

if-else 陳述式

jq 使用 if <test-expression> then <true-expression> else <false-expression> end 支援條件。 您可以在中間新增 elif <test-expression> then <true-expression> 來插入更多案例。 jq 與許多其他語言之間的主要差異在於,每個 thenelse 表示式都會在整體 jq 表達式的後續作業中產生結果。

下列範例示範如何使用 if 語句來產生有條件的資訊。 指定下列輸入︰

{
  "payload": {
    "temperature": 25,
    "humidity": 52
  }
}

使用下列 jq 運算式來檢查溫度是否為高、低或正常:

.payload.status = if .payload.temperature > 80 then
  "high"
elif .payload.temperature < 30 then
  "low"
else
  "normal"
end

在先前的 jq 運算式中:

  • .payload.status = <expression> 將執行 <expression> 的結果指派給承載中的新 status 欄位。
  • if ... end 是核心 if/elif/else 運算式:
    • if .payload.temperature > 80 then "high" 會根據高值檢查溫度,如果為 true,則傳回 "high",否則會繼續。
    • elif .payload.temperature < 30 then "low" 針對低值的溫度執行第二次檢查,如果為 true,請將結果設定為 "low",否則會繼續執行。
    • else "normal" end 傳回 "normal",如果先前的檢查都不是 true,而且會關閉具有 end 的運算式。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": {
    "temperature": 25,
    "humidity": 52,
    "status": "low"
  }
}

地圖

在 jq 之類的功能語言中,執行反覆邏輯最常見的方式是建立陣列,然後將該陣列的值對應至新的陣列。 使用 map 函式在 jq 中達成這項技術,本指南中的許多範例都會出現。 如果您想要對多個值執行某些作業,map 可能是答案。

下列範例示範如何使用 map,從物件的索引鍵中移除前置詞。 這個解決方案可以使用 with_entries 更簡潔地撰寫,但這裏顯示的更詳細資訊版本示範在速記方法中進行的實際對應。 指定下列輸入︰

{
  "payload": {
    "rotor_rpm": 150,
    "rotor_temperature": 51,
    "rotor_cycles": 1354
  }
}

使用下列 jq 運算式,從承載的索引鍵中移除 rotor_ 前置詞:

.payload |= (to_entries | map(.key |= ltrimstr("rotor_")) | from_entries)

在先前的 jq 運算式中:

  • .payload |= <expression> 使用 |=.payload 的值更新為執行 <expression> 的結果。 使用 |= 而不是 =<expression> 的資料內容設為 .payload 而不是 .
  • (to_entries | map(<expression) | from_entries) 會執行物件陣列轉換,並將每個項目對應至具有 <expression> 的新值。 此方法在語意上相當於 with_entries(<expression>)
    • to_entries 會將物件轉換成陣列,每個索引鍵/值組都會成為具有結構 {"key": <key>, "value": <value>} 的個別物件。
    • map(<expression>) 會針對陣列中的每個元素執行 <expression> ,併產生具有每個運算式結果的輸出陣列。
    • from_entriesto_entries 的倒數。 函式會將結構 {"key": <key>, "value": <value>} 的物件陣列轉換成具有 key 的物件,並將 value 欄位對應至索引鍵/值組。
  • .key |= ltrimstr("rotor_") 會使用 ltrimstr("rotor_") 的結果,更新每個項目中 .key 的值。 |= 語法會將右側的資料內容範圍設定為 .key 的值。 如果存在,ltrimstr 會從字串中移除指定的前置詞。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": {
    "rpm": 150,
    "temperature": 51,
    "cycles": 1354
  }
}

減排

減少是跨陣列元素執行迴圈或反覆作業的主要方式。 歸納作業包含累加器,以及使用累加器和陣列目前專案做為輸入的作業。 迴圈的每個反覆項目都會傳回累加器的下一個值,而簡化運算的最終輸出是最後一個累積值。 Reduce 稱為其他一些功能性程式設計語言中的摺疊

使用 jq 中 reduce 作業執行縮減。 大部分的使用案例不需要這種低階操作,而且可以改用較高層級的函式,但 reduce 是實用的一般工具。

下列範例示範如何針對您擁有的資料點計算計量的平均值變更。 指定下列輸入︰

{
  "payload": [
    {
      "value": 65,
      "timestamp": 1689796743559
    },
    {
      "value": 55,
      "timestamp": 1689796771131
    },
    {
      "value": 59,
      "timestamp": 1689796827766
    },
    {
      "value": 62,
      "timestamp": 1689796844883
    },
    {
      "value": 58,
      "timestamp": 1689796864853
    }
  ]
}

使用以下 jq 運算式計算資料點之間值的平均變化:

.payload |= (
  reduce .[] as $item (
    null;
    if . == null then
      {totalChange: 0, previous: $item.value, count: 0}
    else
      .totalChange += (($item.value - .previous) | length)
      | .previous = $item.value
      | .count += 1
    end
  )
  | .totalChange / .count
)

在先前的 jq 運算式中:

  • .payload |= <expression> 使用 |=.payload 的值更新為執行 <expression> 的結果。 使用 |= 而不是 =<expression> 的資料內容設為 .payload 而不是 .
  • reduce .[] as $item (<init>; <expression>) 是一般簡化運算的 Scaffolding,其中包含下列部分:
    • .[] as $item 必須一律是 <expression> as <variable>,而且最常是 .[] as $item<expression> 會產生值的資料流,每個值都會儲存至 <variable>,以供簡化運算的反覆項目使用。 如果您有想要逐一查看的陣列,.[] 將其分割成資料流。 此語法與用來分割訊息的語法相同,但 reduce 作業不會使用資料流來產生多個輸出。 reduce 不會將您的訊息分開。
    • <init> 在此情況下 null ,是縮減作業中使用的累加器初始值。 此值通常設定為空白或零。 這個值會成為第一個反覆項目 <expression> 迴圈中的資料內容 .
    • <expression> 是在簡化運算的每個反覆項目上執行的作業。 它可透過 . 存取目前的累加器值,以及透過稍早宣告之 <variable> 資料流中的目前值,在此案例中為 $item
  • if . == null then {totalChange: 0, previous: $item.value, count: 0} 是處理縮減第一個反覆項目的條件。 它會設定下一個反覆項目的累加器結構。 由於表達式會計算專案之間的差異,因此第一個專案會設定用來在第二個縮減反覆運算上計算差異的數據。 totalChangepreviouscount 欄位可作為迴圈變數,並在每次反覆專案上更新。
  • .totalChange += (($item.value - .previous) | length) | .previous = $item.value | .count += 1else 案例中的運算式。 此運算式會根據計算,將累加器物件中的每個欄位設定為新的值。 針對 totalChange,它會尋找目前和先前值之間的差異,並取得絕對值。 相反地,它會使用 length 函式來取得絕對值。 previous 會設定為目前 $itemvalue,以供下一個反覆運算使用,並且 count 遞增。
  • .totalChange / .count 在簡化運算完成之後計算資料點的平均變更,而且您有最終的累加器值。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": 5.25
}

迴圈

jq 中的迴圈通常會保留給進階使用案例。 由於 jq 中的每個作業都是產生值的運算式,因此大部分語言中循環的陳述式驅動語意並不適合 jq。 請考慮使用 mapreduce 來解決您的需求。

jq 中有兩種傳統迴圈的主要類型。 其他循環類型存在,但適用於更特殊的使用案例:

  • while 會針對輸入資料內容重複套用作業,更新資料內容的值,以供下一個反覆項目使用,並將該值產生為輸出。 while 迴圈的輸出是一個陣列,包含迴圈的每個反覆項目所產生的值。
  • untilwhile 一樣對輸入資料內容重複套用操作,更新資料內容的值以供下一次反覆運算使用。 不同於 whileuntil 迴圈會輸出迴圈最後一次反覆運算所產生的值。

下列範例示範如何使用 until 迴圈,逐步消除讀數清單中的極端值資料點,直到標準差低於預先定義的值為止。 指定下列輸入︰

{
  "payload": [
    {
      "value": 65,
      "timestamp": 1689796743559
    },
    {
      "value": 55,
      "timestamp": 1689796771131
    },
    {
      "value": 59,
      "timestamp": 1689796827766
    },
    {
      "value": 62,
      "timestamp": 1689796844883
    },
    {
      "value": 58,
      "timestamp": 1689796864853
    }
  ]
}

使用下列 jq 運算式,逐漸消除讀數清單中的極端資料點,直到標準差低於 2:

def avg: add / length;
def stdev: avg as $mean | (map(. - $mean | . * .) | add) / (length - 1) | sqrt;
.payload |= (
  sort_by(.value)
  | until(
    (map(.value) | stdev) < 2 or length == 0;
    (map(.value) | avg) as $avg
    | if ((.[0].value - $avg) | length) > ((.[-1].value - $avg) | length) then
      del(.[0])
    else
      del(.[-1])
    end
  )
)

在先前的 jq 運算式中:

  • def avg: add / length; 定義稱為 avg 的新函式,稍後在表達式中用來計算平均值。 : 右邊的運算式是每當您使用 avg 時所用的邏輯運算式。 運算式 <expression> | avg 相當於 <expression> | add / length
  • def stdev: avg as $mean | (map(. - $mean | . * .) | add) / (length - 1) | sqrt; 定義名為 stdev 的新函式。 函式會使用 StackOverflow 上已修改版本的社群回應來計算陣列的範例標準差。
  • .payload |= <expression> 前兩個 def 只是宣告,並啟動實際的運算式。 運算式會使用 .payload 的輸入資料物件執行 <expression>,並將結果指派回 .payload
  • sort_by(.value) 依其 value 欄位排序陣列項目的陣列。 此解決方案會要求您識別及操作陣列中最高和最低的值,因此事先排序資料會減少計算,並簡化程序碼。
  • until(<condition>; <expression>) 會針對輸入執行 <expression>,直到 <condition> 傳回 true 為止。 每次執行 <expression><condition> 的輸入都是先前執行 <expression> 的輸出。 最後執行 <expression> 的結果會從迴圈傳回。
  • (map(.value) | stdev) < 2 or length == 0 是迴圈的條件:
    • map(.value) 會將陣列轉換成純數字清單,以供後續計算使用。
    • (<expression> | stdev) < 2 會計算陣列的標準差,如果標準差小於 2,則會傳回 true。
    • length == 0 取得輸入陣列的長度,如果為 0,則會傳回 true。 若要防止排除所有項目的情況,結果會以整體運算式 or-ed。
  • (map(.value) | avg) as $avg 將陣列轉換為數字陣列並計算它們的平均值,然後將結果儲存到 $avg 變數。 這種方法可節省計算成本,因為您會在迴圈反覆運算中重複使用平均多次。 變數指派運算式不會在 | 之後變更下一個運算式的資料內容,因此其餘計算仍可存取完整陣列。
  • if <condition> then <expression> else <expression> end 是迴圈反覆運算的核心邏輯。 它會使用 <condition> 來判斷要執行和傳回的 <expression>
  • ((.[0].value - $avg) | length) > ((.[-1].value - $avg) | length)if 條件,它將最高值和最低值與平均值進行比較,然後比較這些差異:
    • (.[0].value - $avg) | length 擷取第一個陣列項目的 value 欄位,並取得其與整體平均值之間的差異。 第一個陣列項目是上一個排序的最低值。 此值可能是負值,因此結果會以管線傳送至 length,當指定數位做為輸入時,會傳回絕對值。
    • (.[-1].value - $avg) | length 針對最後一個陣列項目執行相同的作業,並計算絕對值以及安全性。 因為上一個排序,最後一個陣列項目是最高的。 然後使用 >,在整體條件中比較絕對值。
  • del(.[0]) 是當第一個陣列項目是最大的極端值時所執行的 then 運算式。 運算式會從陣列中移除位於 .[0] 的元素。 運算式會在作業之後傳回陣列中留下的資料。
  • del(.[-1]) 是當最後一個陣列項目是最大的極端值時所執行的 else 運算式。 運算式會從陣列中移除位於 .[-1] 的元素,也就是最後一個項目。 運算式會在作業之後傳回陣列中留下的資料。

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "payload": [
    {
      "value": 58,
      "timestamp": 1689796864853
    },
    {
      "value": 59,
      "timestamp": 1689796827766
    },
    {
      "value": 60,
      "timestamp": 1689796844883
    }
  ]
}

卸除訊息

當您撰寫篩選條件運算式時,您可以指示系統傳回 false 來卸除任何您不想要的訊息。 此行為是 jq 中條件運算式的基本行為。 不過,有時候當您轉換訊息或執行更進階的篩選條件時,系統想要明確或隱含地為您卸除訊息。 下列範例示範如何實作此行為。

明確卸除

若要在篩選條件運算式中明確卸除訊息,請從運算式傳回 false

您也可以使用 jq 中的內建 empty 函式,從轉換內卸除訊息。

下列範例示範如何計算訊息中資料點的平均值,並卸除任何低於固定值平均值的訊息。 使用轉換階段和篩選階段的組合,達成此行為是可能的和有效的。 使用最符合您情況的方法。 指定下列輸入:

訊息 1

{
  "payload": {
    "temperature": [23, 42, 63, 61],
    "humidity": [64, 36, 78, 33]
  }
}

訊息 2

{
  "payload": {
    "temperature": [42, 12, 32, 21],
    "humidity": [92, 63, 57, 88]
  }
}

使用下列 jq 運算式來計算資料點的平均值,然後捨棄平均溫度小於 30 或平均濕度大於 90 的任何訊息:

.payload |= map_values(add / length)
| if .payload.temperature > 30 and .payload.humidity < 90 then . else empty end

在先前的 jq 運算式中:

  • .payload |= <expression> 使用 |=.payload 的值更新為執行 <expression> 的結果。 使用 |= 而不是 =<expression> 的資料內容設為 .payload 而不是 .
  • map_values(add / length)add / length會針對子物件中的每個.payload值執行。 運算式會加總值陣列中的元素,然後除以陣列的長度來計算平均值。
  • if .payload.temperature > 30 and .payload.humidity < 90 then . else empty end 會根據產生的訊息檢查兩個條件。 如果篩選條件評估為 true (如同第一個輸入所示),則會產生完整的訊息做為輸出。 如果篩選條件評估為 false (如同第二個輸入所示),它會傳回 empty,這會導致具有零值的空資料流。 此結果會導致運算式卸除對應的訊息。

輸出 1

{
  "payload": {
    "temperature": 47.25,
    "humidity": 52.75
  }
}

輸出 2

(沒有輸出)

使用錯誤隱含卸除

篩選和轉換運算式都可以藉由造成 jq 產生錯誤,以隱含方式卸除訊息。 雖然這種方法不是最佳作法,因為管線無法區分您故意造成的錯誤,以及您運算式未如預期輸入所造成的錯誤。 系統目前會藉由卸除訊息並錄製錯誤,在篩選或轉換中處理運行時錯誤。

使用此方法的常見情節是當管線的輸入可以有結構上脫離的訊息時。 下列範例示範如何接收兩種類型的訊息,其中一種會成功針對篩選條件進行評估,另一種在結構上與運算式不相容。 指定下列輸入:

訊息 1

{
  "payload": {
    "sensorData": {
      "temperature": 15,
      "humidity": 62
    }
  }
}

訊息 2

{
  "payload": [
    {
      "rpm": 12,
      "timestamp": 1689816609514
    },
    {
      "rpm": 52,
      "timestamp": 1689816628580
    }
  ]
}

使用下列 jq 運算式來篩選出溫度小於 10 且濕度大於 80 的訊息:

.payload.sensorData.temperature > 10 and .payload.sensorData.humidity < 80

在上一個範例中,運算式本身是簡單的複合布林運算式。 運算式的設計訴求是使用先前所顯示輸入訊息的第一個結構。 當運算式收到第二則訊息時,.payload 的數位結構與運算式中的物件存取不相容,並產生錯誤。 如果您想要根據溫度/濕度值進行篩選,並移除結構不相容的訊息,此運算式會運作。 造成錯誤的另一種方法是將 (.payload | type) == "object" and 新增至運算式開頭。

輸出 1

true

輸出 2

(錯誤)

時間公用程式

jq 不支援以原生類型的形式使用時間。 不過,數據處理者接受和發出的某些格式會支持作為原生類型的時間。 這些類型通常是使用 Go 的類型 time.Time 來表示。

為了讓您能夠與 jq 中的這些值互動,數據處理者會提供一組函式,讓您:

  • 在原生時間、ISO 8601 字串和數值 Unix 時間戳之間轉換。
  • 針對所有這些類型執行各種時間特定作業。

time 模組

所有特殊時間特定函式都會在模組中 time 指定,您可以匯入查詢。

以下列兩種方式之一,在查詢開頭匯入模組:

  • import" "time" as time;
  • include "time"

第一個方法會將模組中的所有函式放在命名空間下,例如 time::totime。 第二個方法只會將所有時間函式放在最上層,例如 totime。 這兩種語法都是有效且功能相等的。

格式和轉換

時間模組適用於三種時間格式:

  • time 是原生時間值。 您只能將它與時間模組中的函式搭配使用。 串行化時辨識為 time 數據類型。
  • unix 是一個數值 Unix 時間戳,表示自 Unix epoch 以來的秒數。 它可以是整數或浮點數。 串行化時可辨識為對應的數值類型。
  • iso 是 ISO 8601 字串格式的時間表示。 串行化時辨識為字串。

時間模組提供下列函式來檢查及操作這些類型:

  • time::totime 會將這三種類型中的任何一個轉換為 time
  • time::tounix 會將這三種類型中的任何一個轉換為 unix
  • time::toiso 會將這三種類型中的任何一個轉換為 iso
  • time::istime 如果數據的格式為 time true,則傳回 true。

時間作業

時間模組提供對所有類型運作的各種特定時間作業。 下列函式可以接受任何支援的型別作為其輸入,並傳回與其輸出相同的類型。 如果需要精確度,整數時間戳可能會轉換成浮點時間戳。

  • time::utc 將時間轉換為UTC。
  • time::zone(zone) 將時間轉換為提供的區域。 zone 是 ISO 8601 區域字串。 例如: time::zone("-07")
  • time::local 會將時間轉換為當地時間。
  • time::offset(duration) 會依提供的持續時間來位移時間。 duration 會使用 Go 的持續時間字串語法。 例如: time::offset("1m2s")
  • time::offset(value;unit) 會依提供的持續時間來位移時間。 此函式會使用數位和單位字串。 例如: time::offset(2;"s") 。 當持續時間來自另一個屬性時,此函式很有用。

注意

這三個時區函式對 Unix 時間戳沒有有意義的影響。

其他公用程式

模組 util 是公用程式集合,可擴充 jq 運行時間的功能。

util 模組

所有雜項公用程式都會在您可以匯入查詢的模組中 util 指定。

以下列兩種方式之一,在查詢開頭匯入模組:

  • import" "util" as util;
  • include "util"

第一個方法會將模組中的所有函式放在命名空間下,例如 util::uuid。 第二個方法只會將所有雜項函式放在最上層,例如 uuid。 這兩種語法都是有效且功能相等的。

模組 util 目前包含 uuid 以標準字串格式傳回新隨機 UUID 的函式。

二進位操作

jq 的設計目的是要處理可表示為 JSON 的數據。 不過,數據處理者管線也支援保留未剖析二進位數據的原始數據格式。 若要使用二進位數據,隨附於數據處理者的 jq 版本包含可協助您處理二進位數據的套件。 讓您能夠:

  • 在二進位和其他格式之間來回轉換,例如 base64 和整數陣列。
  • 使用內建函式從二進位訊息讀取數值和字串值。
  • 執行二進位資料的點編輯,同時仍保留其格式。

重要

您無法使用任何修改二進位值的內建 jq 函式或運算子。 這表示沒有與 + 串連、沒有針對位元組運作的 map,而且沒有具有二進位值的混合指派,例如 |=+=//=。 您可以使用標準指派 (==)。 如果您嘗試搭配不支援的作業使用二進位資料,系統會擲回 jqImproperBinaryUsage 錯誤。 如果您需要以自訂方式操作二進位資料,請考慮使用下列其中一個函式將它轉換成 base64 或計算的整數陣列,然後將它轉換回二進位。

下列各節說明數據處理者 jq 引擎中的二進位支援。

binary 模組

數據處理者 jq 引擎中的所有二進位支援都會在您可以匯入的模組中 binary 指定。

以下列兩種方式之一,在查詢開頭匯入模組:

  • import "binary" as binary;
  • include "binary"

第一個方法會將模組中的所有函式放在命名空間下,例如 binary::tobase64。 第二個方法只會將所有二進位函式放在最上層,例如 tobase64。 這兩種語法都是有效且功能相等的。

格式和轉換

二進位模組適用於三種類型:

  • 二進位 - 二進位值,只能直接搭配二進位模組中的函式使用。 當序列化時,管線可辨識為二進位資料類型。 將此類型用於原始序列化。
  • 陣列 - 將二進位轉換成數字陣列的格式,可讓您執行自己的處理。 當序列化時,管線會辨識為整數陣列。
  • base64 - 二進位的字串格式表示。 如果您想要在二進位和字串之間轉換,則最有用。 當序列化時,管線可辨識為字串。

您可以根據您的需求,在 jq 查詢中的所有三種類型之間轉換。 例如,您可以從二進位轉換成陣列、執行一些自訂操作,然後在結尾轉換成二進位,以保留類型資訊。

函式

提供下列函式來檢查和操作這些類型:

  • binary::tobinary 會將這三種類型中的任何一個轉換成二進位。
  • binary::toarray 會將這三種類型中的任何一個轉換成陣列。
  • binary::tobase64 會將這三種類型中的任何一個轉換成 base64。
  • 如果資料是二進位格式,binary::isbinary 會傳回 true。
  • 如果資料是陣列格式,binary::isarray 會傳回 true。
  • 如果資料是 base64 格式,binary::isbase64 會傳回 true。

此模組也提供 binary::edit(f) 函式,以便快速編輯二進位資料。 函式會將輸入轉換成陣列格式、套用函式,然後將結果轉換回二進位。

從二進位擷取資料

二進位模組可讓您從二進位資料擷取值,以用於解除封裝自訂二進位承載。 一般而言,這項功能會遵循其他二進位解壓縮程式庫的套件,並遵循類似的命名。 下列類型可以解除封裝:

  • 整數 (int8, int16, int32, int64, uint8, uint16, uint32, uint64)
  • 浮點數 (float, double)
  • 字串 (utf8)

模組也可在適用的情況下指定位移和位元組順序。

讀取二進位資料的函式

二進位模組提供下列函式,可從二進位值擷取資料。 您可以使用所有函式搭配套件可轉換的三種類型中的任何一種。

所有函式參數都是選擇性的,offset 預設為 0,而 length 預設為其餘資料。

  • binary::read_int8(offset) 會從二進位值讀取 int8。
  • binary::read_int16_be(offset) 以大端順序從二進位值讀取 int16。
  • binary::read_int16_le(offset) 以小端順序從二進位值讀取 int16。
  • binary::read_int32_be(offset) 以大端順序從二進位值讀取 int32。
  • binary::read_int32_le(offset) 以小端順序從二進位值讀取 int32。
  • binary::read_int64_be(offset) 以大端順序從二進位值讀取 int64。
  • binary::read_int64_le(offset) 以小端順序從二進位值讀取 int64。
  • binary::read_uint8(offset) 從二進位值讀取 uint8。
  • binary::read_uint16_be(offset) 以大端順序從二進位值讀取 uint16。
  • binary::read_uint16_le(offset) 以小端順序從二進位值讀取 uint16。
  • binary::read_uint32_be(offset) 以大端順序從二進位值讀取 uint32。
  • binary::read_uint32_le(offset) 以小端順序從二進位值讀取 uint32。
  • binary::read_uint64_be(offset) 以大端順序從二進位值讀取 uint64。
  • binary::read_uint64_le(offset) 以小端順序從二進位值讀取 uint64。
  • binary::read_float_be(offset) 以大端順序從二進位值讀取浮點數。
  • binary::read_float_le(offset) 以小端順序從二進位值讀取浮點數。
  • binary::read_double_be(offset) 以大端順序從二進位值讀取雙精度浮點數。
  • binary::read_double_le(offset) 以小端順序從二進位值讀取雙精度浮點數。
  • binary::read_bool(offset; bit) 從二進位值讀取 bool,並檢查指定的位元是否有值。
  • binary::read_bit(offset; bit) 使用指定的位元索引,從二進位值讀取位元。
  • binary::read_utf8(offset; length) 從二進位值讀取 UTF-8 字串。

寫入二進位資料

二進位模組可讓您編碼和寫入二進位值。 這項功能可讓您直接在 jq 中建構或編輯二進位承載。 寫入資料支援與資料擷取相同的資料類型集合,也可讓您指定要使用的位元組順序。

資料的寫入有兩種形式:

  • write_* 函式在二進位值中就地更新資料,用來更新或操作現有的值。
  • append_* 函式將資料加入二進位值的結尾,用來加入或建構新的二進位值。

用來寫入二進位資料的函式

二進位模組提供下列函式,將資料寫入二進位值。 所有函式都可以針對此套件可轉換的三個有效類型中的任何一種執行。

所有函式都需要 value 參數,但 offset 是選擇性的,其中有效且預設為 0

寫入函式:

  • binary::write_int8(value; offset) 將 int8 寫入二進位值。
  • binary::write_int16_be(value; offset) 以大端順序將 int16 寫入二進位值。
  • binary::write_int16_le(value; offset) 以小端順序將 int16 寫入二進位值。
  • binary::write_int32_be(value; offset) 以大端順序將 int32 寫入二進位值。
  • binary::write_int32_le(value; offset) 以小端順序將 int32 寫入二進位值。
  • binary::write_int64_be(value; offset) 以大端順序將 int64 寫入二進位值。
  • binary::write_int64_le(value; offset) 以小端順序將 int64 寫入二進位值。
  • binary::write_uint8(value; offset) 將 uint8 寫入二進位值。
  • binary::write_uint16_be(value; offset) 以大端順序將 uint16 寫入二進位值。
  • binary::write_uint16_le(value; offset) 以小端順序將 uint16 寫入二進位值。
  • binary::write_uint32_be(value; offset) 以大端順序將 uint32 寫入二進位值。
  • binary::write_uint32_le(value; offset) 以小端順序將 uint32 寫入二進位值。
  • binary::write_uint64_be(value; offset) 以大端順序將 uint64 寫入二進位值。
  • binary::write_uint64_le(value; offset) 將 uint64 寫入以小端順序的二進位值。
  • binary::write_float_be(value; offset) 以大端順序將浮點數寫入二進位值。
  • binary::write_float_le(value; offset) 以小端順序將浮點數寫入二進位值。
  • binary::write_double_be(value; offset) 以大端順序將雙精度浮點數寫入二進位值。
  • binary::write_double_le(value; offset) 以小端順序將雙精度浮點數寫入二進位值。
  • binary::write_bool(value; offset; bit) 將 bool 寫入二進位值中的單一位元組,並將指定的位元設定為 bool 值。
  • binary::write_bit(value; offset; bit) 在二進位值中寫入單一位元,讓位元組中的其他位元保持一致。
  • binary::write_utf8(value; offset) 將 UTF-8 字串寫入二進位值。

附加函式:

  • binary::append_int8(value) 將 int8 附加至二進位值。
  • binary::append_int16_be(value) 以大端順序將 int16 附加至二進位值。
  • binary::append_int16_le(value) 以小端順序將 int16 附加至二進位值。
  • binary::append_int32_be(value) 以大端順序將 int32 附加至二進位值。
  • binary::append_int32_le(value) 以小端順序將 int32 附加至二進位值。
  • binary::append_int64_be(value) 以大端順序將 int64 附加至二進位值。
  • binary::append_int64_le(value) 以小端順序將 int64 附加至二進位值。
  • binary::append_uint8(value) 將 uint8 附加至二進位值。
  • binary::append_uint16_be(value) 以大端順序將 uint16 附加至二進位值。
  • binary::append_uint16_le(value) 以小端順序將 uint16 附加至二進位值。
  • binary::append_uint32_be(value) 以大端順序將 uint32 附加至二進位值。
  • binary::append_uint32_le(value) 以小端順序將 uint32 附加至二進位值。
  • binary::append_uint64_be(value) 以大端順序將 uint64 附加至二進位值。
  • binary::append_uint64_le(value) 以小端順序將 uint64 附加至二進位值。
  • binary::append_float_be(value) 以大端順序將浮點數附加至二進位值。
  • binary::append_float_le(value) 以小端順序將 float 附加至二進位值。
  • binary::append_double_be(value) 以大端順序將雙精度浮點數附加至二進位值。
  • binary::append_double_le(value) 以小端順序將雙精度浮點數附加至二進位值。
  • binary::append_bool(value; bit) 將 bool 附加至二進位值中的單一位元組,並將指定的位元設定為 bool 值。
  • binary::append_utf8(value) 將 UTF-8 字串附加至二進位值。

二進位範例

本節說明使用二進位資料的一些常見使用案例。 這些範例會使用一般輸入訊息。

假設您有包含多個區段之自訂二進位格式承載的訊息。 每個區段都包含以大端位元組順序排列的下列資料:

  • uint32,以位元組為單位保留功能變數名稱的長度。
  • utf-8 字串,其中包含前一個 uint32 所指定長度的功能變數名稱。
  • 雙精度浮點數,保留欄位值。

在此範例中,您有三個區段,並持有:

  • (uint32) 11

  • (utf-8) 溫度

  • (double) 86.0

  • (uint32) 8

  • (utf-8) 濕度

  • (double) 51.290

  • (uint32) 8

  • (utf-8) 壓力

  • (double) 346.23

當列印在訊息的 payload 區段中時,此資料看起來會像這樣:

{
  "payload": "base64::AAAAC3RlbXBlcmF0dXJlQFWAAAAAAAAAAAAIaHVtaWRpdHlASaUeuFHrhQAAAAhwcmVzc3VyZUB1o64UeuFI"
}

注意

二進位資料的 base64::<string> 表示法只是為了方便與其他類型區分,而且不會在處理期間代表實體資料格式。

直接擷取值

如果您知道訊息的確切結構,您可以使用適當的位移從訊息擷取值。

使用下列 jq 運算式來擷取值:

import "binary" as binary;
.payload | {
  temperature: binary::read_double_be(15),
  humidity: binary::read_double_be(35),
  pressure: binary::read_double_be(55)
}

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "humidity": 51.29,
  "pressure": 346.23,
  "temperature": 86
}

動態擷取值

如果訊息可以包含任何順序的任何欄位,您可以動態擷取完整的訊息:

使用下列 jq 運算式來擷取值:

import "binary" as binary;
.payload
| {
    parts: {},
    rest: binary::toarray
}
|
until(
    (.rest | length) == 0;
    (.rest | binary::read_uint32_be) as $length
    | {
        parts: (
            .parts +
            {
                (.rest | binary::read_utf8(4; $length)): (.rest | binary::read_double_be(4 + $length))
            }
        ),
        rest: .rest[(12 + $length):]
    }
)
| .parts

下列 JSON 顯示上一個 jq 運算式的輸出:

{
  "humidity": 51.29,
  "pressure": 346.23,
  "temperature": 86
}

直接編輯值

此範例示範如何編輯其中一個值。 如同擷取案例,如果您知道您要編輯的值在二進位資料中的位置,則會更容易。 此範例示範如何將溫度從華氏轉換為攝氏。

使用下列 jq 運算式,將二進位訊息中的溫度從華氏轉換為攝氏:

import "binary" as binary;
15 as $index
| .payload
| binary::write_double_be(
    ((5 / 9) * (binary::read_double_be($index) - 32));
    $index
)

下列 JSON 顯示上一個 jq 運算式的輸出:

"base64::AAAAC3RlbXBlcmF0dXJlQD4AAAAAAAAAAAAIaHVtaWRpdHlASaUeuFHrhQAAAAhwcmVzc3VyZUB1o64UeuFI"

如果您套用先前顯示的擷取邏輯,您會取得下列輸出:

{
  "humidity": 51.29,
  "pressure": 346.23,
  "temperature": 30
}

動態編輯值

此範例示範如何藉由在查詢中動態尋找所需的值,以達到與上一個範例相同的結果。

使用下列 jq 運算式,將二進位訊息中的溫度從華氏轉換為攝氏,以動態方式尋找要編輯的資料:

import "binary" as binary;
.payload
| binary::edit(
    {
        index: 0,
        data: .
    }
    | until(
        (.data | length) <= .index;
        .index as $index
        | (.data | binary::read_uint32_be($index)) as $length
        | if (.data | binary::read_utf8($index + 4; $length)) == "temperature" then
            (
                (.index + 4 + $length) as $index
                | .data |= binary::write_double_be(((5 / 9) * (binary::read_double_be($index) - 32)); $index)
            )
        end
        | .index += $length + 12
    )
    | .data
)

下列 JSON 顯示上一個 jq 運算式的輸出:

"base64::AAAAC3RlbXBlcmF0dXJlQD4AAAAAAAAAAAAIaHVtaWRpdHlASaUeuFHrhQAAAAhwcmVzc3VyZUB1o64UeuFI"

插入新值

使用封裝的 append 函式來新增值。 例如,若要將具有值 31.678windSpeed 欄位新增至輸入,同時保留傳入二進位格式,請使用下列 jq 運算式:

import "binary" as binary;
"windSpeed" as $key
| 31.678 as $value
| .payload
| binary::append_uint32_be($key | length)
| binary::append_utf8($key)
| binary::append_double_be($value)

下列 JSON 顯示上一個 jq 運算式的輸出:

"base64:AAAAC3RlbXBlcmF0dXJlQFWAAAAAAAAAAAAIaHVtaWRpdHlASaUeuFHrhQAAAAhwcmVzc3VyZUB1o64UeuFIAAAACXdpbmRTcGVlZEA/rZFocrAh"