jsonjq

Map Keys with value of Subkeys in jq


I try to reformat the output of the sensors command:

sensors -j ftsteutates-i2c-0-73
{
   "ftsteutates-i2c-0-73":{
      "Adapter": "SMBus I801 adapter at efa0",
      "VCC 3.3V":{
         "in0_input": 3.331
      },
      "3.3V AUX":{
         "in1_input": 3.359
      },
      "V_IN (12V)":{
         "in2_input": 12.012
      },
      "VBAT 3.0V":{
         "in3_input": 2.692
      },
      "CPU":{
         "fan1_input": 660.000,
         "fan1_alarm": 0.000,
         "fan1_fault": 0.000
      },
      "Chassis":{
         "fan3_input": 1200.000,
         "fan3_alarm": 0.000,
         "fan3_fault": 0.000
      },
      "CPU":{
         "temp1_input": 51.000,
         "temp1_alarm": 0.000,
         "temp1_fault": 0.000
      },
      "Ambient":{
         "temp2_input": 44.000,
         "temp2_alarm": 0.000,
         "temp2_fault": 0.000
      },
      "Core":{
         "temp3_input": 54.000,
         "temp3_alarm": 0.000,
         "temp3_fault": 0.000
      },
      "Memory":{
         "temp4_input": 42.000,
         "temp4_alarm": 0.000,
         "temp4_fault": 0.000
      },
      "PCH":{
         "temp5_input": 54.000,
         "temp5_alarm": 0.000,
         "temp5_fault": 0.000
      },
      "Graphics":{
         "temp6_input": 40.000,
         "temp6_alarm": 0.000,
         "temp6_fault": 0.000
      }
   }
}

What I am expecting is:

sensors -j ftsteutates-i2c-0-73 | jq '."ftsteutates-i2c-0-73" | del(."Adapter") | ???' 
{
  "VCC 3.3V": 3.331,
  "3.3V AUX": 3.359,
  "V_IN (12V)": 12.012,
  "VBAT 3.0V": 2.692,
  "CPU": 51.000,
  "Chassis": 1200.000,
  "Ambient": 44.000,
  "Core": 54.000,
  "Memory": 42.000,
  "PCH": 54.000,
  "Graphics": 40.000
}

But I cant figure out how to Map the values of the subkeys and also delete the *_alarm and *_fault subkeys or select just the first subkey which is always the *_input key.

EDIT: I found, that the temp and fan values from CPU get joined, which need to be keeped.

Maybe it is possible to match the part of the subkey name in/fan/temp and join it together in the output name like:

sensors -j ftsteutates-i2c-0-73 | jq '."ftsteutates-i2c-0-73" | del(."Adapter") | ???' 
{
  "VCC 3.3V_in": 3.331,
  "3.3V AUX_in": 3.359,
  "V_IN (12V)_in": 12.012,
  "VBAT 3.0V_in": 2.692,
  "CPU_fan": 51.000,
  "Chassis_fan": 1200.000,
  "Ambient_temp": 44.000,
  "Core_temp": 54.000,
  "Memory_temp": 42.000,
  "PCH_temp": 54.000,
  "Graphics_temp": 40.000
}

Somehow like (dosnt work)

sensors -j ftsteutates-i2c-0-73 | jq '."ftsteutates-i2c-0-73" | del(."Adapter") | with_entries(.key |= gsub(" "; "_") | .value |= with_entries(select(.key | endswith("_input")) | .key |= gsub("_input"; "_in") | .key |= gsub("temp"; "_temp") | .key |= gsub("fan"; "_fan"))) | with_entries(.value |= if type == "object" then .[] else . end)'

or

sensors -j ftsteutates-i2c-0-73 | jq '."ftsteutates-i2c-0-73" | del(."Adapter") | with_entries(.value |= if type == "object" then with_entries(select(.key | endswith("_input")) | .key |= gsub("_input"; "_in") | .key |= gsub("temp"; "_temp") | .key |= gsub("fan"; "_fan")) else . end) | with_entries(.value |= if type == "object" then .[] else . end)'

Solution

  • Here's another way using the error suppression operator ? (with jq 1.7+):

    jq '."ftsteutates-i2c-0-73" | .[] |= (.[keys[] | select(endswith("_input"))])?'
    

    Demo

    For older versions, just add another types filter for objects (with jq 1.6+):

    jq '."ftsteutates-i2c-0-73" | .[] |= (objects | .[keys[] | select(endswith("_input"))])'
    

    Demo

    Not recommended, but if the field ending with _input is always encoded as the first one, you could even shorten this with jq 1.7+ to:

    jq '."ftsteutates-i2c-0-73" | .[] |= .[]?'   # or .[] |= objects[]
    

    Demo

    Result (due to its type, the Adapter field was deleted implicitly):

    {
      "VCC 3.3V": 3.331,
      "3.3V AUX": 3.359,
      "V_IN (12V)": 12.012,
      "VBAT 3.0V": 2.692,
      "CPU": 51.000,
      "Chassis": 1200.000,
      "Ambient": 44.000,
      "Core": 54.000,
      "Memory": 42.000,
      "PCH": 54.000,
      "Graphics": 40.000
    }
    

    EDIT: I found, that the temp and fan values from CPU get joined, which need to be keeped.

    Maybe it is possible to match the part of the subkey name in/fan/temp and join it together in the output name

    You'd need to --stream the input at invocation in order to process broken-down parts individually, before they are collapsed in a superordinate structure.

    Here's one way using just from_entries on a prepared array:

    jq --stream -n '
      [inputs | select(.[0][0] == "ftsteutates-i2c-0-73") | {
        key: "\(.[0][1])_\(.[0][2] | strings | scan("^(.*)\\d+_input$")[0])", 
        value: .[1] | values
      }] | from_entries
    '
    

    Here's another one using reduce to successively build up the output:

    jq --stream -n '
      reduce (inputs | select(has(1))) as [$path, $val]
        (.; setpath($path | .[1:] |= [join("_")]; $val))
      | ."ftsteutates-i2c-0-73"
      | with_entries(.key = (.key | scan("^(.*)\\d+_input$")[0]))
    '
    

    Result:

    {
      "VCC 3.3V_in": 3.331,
      "3.3V AUX_in": 3.359,
      "V_IN (12V)_in": 12.012,
      "VBAT 3.0V_in": 2.692,
      "CPU_fan": 660.000,
      "Chassis_fan": 1200.000,
      "CPU_temp": 51.000,
      "Ambient_temp": 44.000,
      "Core_temp": 54.000,
      "Memory_temp": 42.000,
      "PCH_temp": 54.000,
      "Graphics_temp": 40.000
    }