JQ Functions

JQJQ Functions - 图1 (opens new window) is a powerful command line tool and programming language designed primarily for transforming and querying data encoded as JSONJQ Functions - 图2 (opens new window).

EMQX rules allow users to define SQL-like rules to process and route messages. These rules can include JQ functions to perform complex transformations on JSON payloads as they pass through the broker.

If you are new to JQ function, you can refer to the Reference section to speedily get started.

TIP

JQ functions can be convenient for transformations that are difficult to do with only the rule SQL language.

However, to maintain efficient message processing, you are recommended to avoid long-running computations in the rule and use the timeouts feature (with configuration item rule_engine.jq_function_default_timeout) to prevent buggy JQ programs.

The JQ Function

The basic format of a jq statement in the rule engine SQL is as follows:

  1. jq('<JQ_program>', '<JSON_input>', <timeout_value>)

Where,

  1. <JQ_program>: A string containing a valid JQ program.
  2. <JSON_input>: A JSON encoded string or object as the input for the JQ program.
  3. <timeout_value>: An optional integer timeout value in milliseconds, with a default value of 10 seconds.’’

The jq function returns a list of objects generated by the given JQ program when provided with the specified input. If the execution doesn’t finish before the timeout or if the JQ program encounters an exception, the function will throw an error.

Use Cases

The following are some examples of simple jq function calls and their results:

JSON Data Manipulation

This example illustrates various ways to manipulate JSON data using jq, including accessing, transforming, and calculating values within JSON objects,

Code Example:

  1. jq('.', '{"temperature": 10}') =
  2. [json_decode('{"temperature": 10}')]
  3. jq('.', json_decode('{"temperature": 10}')) =
  4. [json_decode('{"temperature": 10}')]
  5. jq('.temperature', '{"temperature": 10}') =
  6. [10]
  7. jq('{temperature_C:.temperature,
  8. temperature_F: (.temperature * 1.8 + 32)}',
  9. '{"temperature": 10}') =
  10. [json_decode('{"temperature_C": 10, "temperature_F": 50}')]
  11. jq('.temperature,(.temperature * 1.8 + 32)', '{"temperature": 10}') =
  12. [10, 50]

Calculate Averages by Removing Outliers

For example, the below JSON object contains a date and an array of sensors, each with a name and a set of data points, representing sensor readings on a specific date.

  1. {
  2. "date": "2020-04-24",
  3. "sensors": [
  4. {
  5. "name": "a",
  6. "data": [3, 1, 2, 4, 5, 5]
  7. },
  8. {
  9. "name": "b",
  10. "data": [1, -100, 2, 3, 4, 5, 2000]
  11. },
  12. {
  13. "name": "c",
  14. "data": [3, 7, 9]
  15. }
  16. ]
  17. }

You can combine the jq function with the FOREACH statement to divide JQ’s output objects into multiple messages, with each containing one field for the date and one field for the average of the sensor’s data field after removing outliers.

  1. FOREACH jq('def rem_first:
  2. if length > 2 then del(.[0]) else . end;
  3. def rem_last:
  4. if length > 1 then del(.[-1]) else . end;
  5. .date as $date |
  6. .sensors[] |
  7. (.data | sort | rem_first | rem_last | add / length) as $average |
  8. {$average, $date}',
  9. payload)
  10. FROM "jq_demo/complex_rule/jq/#"

Then the three output messages will have the following payloads:

Message 1:

  1. {
  2. "average": 3.5,
  3. "date": "2020-04-24"
  4. }

Message 2:

  1. {
  2. "average": 3,
  3. "date": "2020-04-24"
  4. }

Message 3:

  1. {
  2. "average": 7,
  3. "date": "2020-04-24"
  4. }

Split One Messsage into Separate Messages

The example code processes an input message containing multiple sensor measurements and splits it into separate messages for each sensor type. This is how it works:

  • The FOREACH uses the JQ function to transform input message is transformed into an array of objects containing sensor_type and value fields.
  • The DO clause selects relevant fields for the output messages
  • The FROM clause applies the rule to messages with a matching topic filter, car/measurements.
  1. FOREACH
  2. ## The data must be an array
  3. jq('
  4. [{
  5. sensor_type: "temperature",
  6. value: .temperature
  7. },
  8. {
  9. sensor_type: "humidity",
  10. value: .humidity
  11. },
  12. {
  13. sensor_type: "pressure",
  14. value: .pressure
  15. },
  16. {
  17. sensor_type: "light",
  18. value: .light
  19. },
  20. {
  21. sensor_type: "battery",
  22. value: .battery
  23. },
  24. {
  25. sensor_type: "speed",
  26. value: .speed
  27. }]',
  28. payload) as sensor
  29. DO
  30. payload.client_id,
  31. payload.timestamp,
  32. sensor.sensor_type,
  33. sensor.value
  34. FROM "car/measurements"

Alternative Way for Splitting Messages

This example illustrates an alternative approach to splitting an input message containing multiple sensor measurements into separate messages for each sensor type.

The JQ function within the FOREACH clause saves the input and all sensor types, then outputs an object for each sensor type with relevant fields.

  1. FOREACH
  2. jq('
  3. # Save the input
  4. . as $payload |
  5. # All sensor types
  6. [
  7. "temperature",
  8. "humidity",
  9. "pressure",
  10. "light",
  11. "battery",
  12. "speed"
  13. ] as $sensor_types |
  14. # Output an object for each sensor type
  15. $sensor_types[] |
  16. {
  17. client_id: $payload.client_id,
  18. timestamp: $payload.timestamp,
  19. sensor_type: .,
  20. value: $payload[.]
  21. }
  22. ',
  23. payload) as sensor
  24. FROM "car/measurements"

References

If you are new to the JQ function, the following materials are recommended: