JSON array parsing

Is it possible to parse a JSON array in an Openhab rule? Or is this not possible with a rule?

I want to parse following JSON: (example with data of 3 devices)

{“Method”: “devices.status”,“Params”: [{“Devices”: [{“Properties”: [{“Status”: “Heating”},{“Temperature”: 21},{“Type”: “Thermostat”}],“ID”: “Device_1”},{“Properties”: [{“Status”: “On”},{“Type”: “Switch”}],“ID”: “Device_2”},{“Properties”: [{“Status”: “Off”},{“Type”: “Switch”}],“ID”: “Device_3”}]}]}

I tried using transform jsonpath but I only able to parse the data of the first device and the data of the other devices gets lost.

What is the best way to do this? I want to end up with the data of the 3 devices whitout losing any of it.

Device_1:

  • Status = heating
  • Temprature = 21
  • Type = thermostat

Device_2:

  • Status = ON
  • Type = switch

Device_3:

  • Status = OFF
  • Type = switch

After paring the data I want to use a rule to do some stuff depending on eg. the status of device_3.

Yes.
If you want to use the JSONPATH transformation, you’ll probably do one element at a time, because the transformation only returns one string.
Examples

You can get it to return a single string that looks like an array “[blah, bleh]” which you can parse further in your rule (e.g. split on commas) but likely easier to get one at a time.
Complex example doing that

I did JSON parsing in a Javascript rule.
//need below to log to openhab.log file
var logger = Java.type(‘org.slf4j.LoggerFactory’).getLogger(‘org.openhab.rule.’ + ctx.ruleUID);


//below is json parse
var obj = itemRegistry.getItem("BOMdata").getState();
var test = JSON.parse(obj) ;


for (var count = 0; count < 7; count++) {
logger.info('Icon: ' + test.data[count].icon_descriptor);
logger.info('Short text: ' + test.data[count].short_text);
logger.info('Date: ' + test.data[count].date);
logger.info('Max: ' + test.data[count].temp_max);
logger.info('Min: ' + test.data[count].temp_min);
logger.info('Forecast ' + test.data[count].extended_text);
}

also like this:

//parse json data:
  var test = JSON.parse('{"localNumber":"012345","remoteNumber":"012345","date":"2021-06-26T17:55:00+02","type":2,"duration":0}') 
logger.info("test: " + test.remoteNumber)
logger.info("date: " + test.date)

You probably already figured it out but you just ask for specific peace of data you want.

Something like

rule "Transform json message to devices"
when
    Item Incoming_json changed
then

  val Device_1_Status      = transform("JSONPATH", "$.Params[*].Devices[?(@.ID == 'Device_1')].Properties[*].Status", Incoming_json.state.toString)
  val Device_1_Temperature = transform("JSONPATH", "$.Params[*].Devices[?(@.ID == 'Device_1')].Properties[*].Temperature", Incoming_json.state.toString)
  val Device_1_Type        = transform("JSONPATH", "$.Params[*].Devices[?(@.ID == 'Device_1')].Properties[*].Type", Incoming_json.state.toString)

  val Device_2_Status      = transform("JSONPATH", "$.Params[*].Devices[?(@.ID == 'Device_2')].Properties[*].Status", Incoming_json.state.toString)
  val Device_2_Type        = transform("JSONPATH", "$.Params[*].Devices[?(@.ID == 'Device_2')].Properties[*].Type", Incoming_json.state.toString)

  val Device_3_Status      = transform("JSONPATH", "$.Params[*].Devices[?(@.ID == 'Device_3')].Properties[*].Status", Incoming_json.state.toString)
  val Device_3_Type        = transform("JSONPATH", "$.Params[*].Devices[?(@.ID == 'Device_3')].Properties[*].Type", Incoming_json.state.toString)

    Light_Hall.postUpdate(Device_3_Status)
    

end

Then you can just do another rule with the data coming in you just go to know the difference between update and command

rule "Device 3 status changes"
when
    Item Light_Hall changed
then
  "Do something"
end
1 Like

@denominator Hey thank you for the clear example!

The amount of devices can vary (can be 3 like in the example but can also be 20), is that a problem?

As long that I parse out the data of the devices I need it is ok I think?
Like for example:

val Device_1_Status = transform(“JSONPATH”, “$.Params[].Devices[?(@.ID == ‘Device_1’)].Properties[].Status”, Incoming_json.state.toString)
val Device_1_Temperature = transform(“JSONPATH”, “$.Params[].Devices[?(@.ID == ‘Device_1’)].Properties[].Temperature”, Incoming_json.state.toString)

val Device_13_Status = transform(“JSONPATH”, “$.Params[].Devices[?(@.ID == ‘Device_13’)].Properties[].Status”, Incoming_json.state.toString)

val Device_24_Status = transform(“JSONPATH”, “$.Params[].Devices[?(@.ID == ‘Device_24’)].Properties[].Status”, Incoming_json.state.toString)

The data of the devices that I don’t need can be skipped.

Personally I would create a thing and do the transform as part of that. Create a channel in the thing for each bit of data.

Thing Device_1

Channel 1 Status
Channel 2 Temps
And so on

Then you link the channel to items.

What version of OH are you using?
How do you create things (UI or CONFIG)?
How is the JSON data coming into openHAB?

@denominator I’m just upgraded my setup from OH 2.4 to OH 3.1. (and added also some new hardware in my house).
I use config files (*.items, *.rules, …) to create my whole configuration.
The JSON data (string) is received by Openhab via one MQTT toppic.
So I don’t think it is possible to create different things/channels for it.

Somehow, somewhere you have also created some kind of Thing for your MQTT data.
The suggestion is to create more MQTT channels (which can all share the same topic) that use different transformations to pick out individual data, and link directly to Items. no rules required.

1 Like

I didn’t know it was possible to use the transformations when creating the channels. I’m going to take a deeper look into that also.
At this moment I’m testting a rule based on the provided solution of denominator.

Its an example of what you want maybe not the solution as you asked how to do it in a rule.

@rossko57 suggestion is how I would do it in the first place if the OP question was different. You can do it however you like. I would do it way he suggested.

Bridge mqtt:broker:MQTTBroker [ host ="192.168.1.141", secure =false, username ="un", password ="pw" , clientID ="myMQTTClient" ]
{
 
    Thing topic device_1 "HVAC" {
    Channels:
        Type string : status      "HVAC Mode"        [ stateTopic = "JSON?INCOMMING", transformationPattern = "JSONPATH:$.Params[*].Devices[?(@.ID == 'Device_1')].Properties[*].Status", commandTopic = "SOMETHING" ]
        Type number : temperature "Set Temperature"  [ stateTopic = "JSON?INCOMMING", transformationPattern = "JSONPATH:$.Params[*].Devices[?(@.ID == 'Device_1')].Properties[*].Temperature", commandTopic ="SOMETHING" ]
        Type string : type        "Label"            [ stateTopic = "JSON?INCOMMING", transformationPattern = "JSONPATH:$.Params[*].Devices[?(@.ID == 'Device_1')].Properties[*].Type" ]
      }

    Thing topic device_2 "Light Hallway" {
    Channels:
        Type switch : status "Light"  [ stateTopic = "JSON?INCOMMING", transformationPattern = "JSONPATH:$.Params[*].Devices[?(@.ID == 'Device_2')].Properties[*].Status", commandTopic = "SOMETHING", On="ON", Off="OFF" ]
        Type string : type   "Label"  [ stateTopic = "JSON?INCOMMING", transformationPattern = "JSONPATH:$.Params[*].Devices[?(@.ID == 'Device_2')].Properties[*].Type" ]
      }

    Thing topic device_2 "Other Device" {
    Channels:
        Type switch : status "State"  [ stateTopic = "JSON?INCOMMING", transformationPattern = "JSONPATH:$.Params[*].Devices[?(@.ID == 'Device_3')].Properties[*].Status", commandTopic = "SOMETHING", On="ON", Off="OFF" ]
        Type string : type   "Label"  [ stateTopic = "JSON?INCOMMING", transformationPattern = "JSONPATH:$.Params[*].Devices[?(@.ID == 'Device_3')].Properties[*].Type" ]
      }

}

Note I put in Mapping as per json string supplied .

The items would look like depending on you broker name. You only want/need one broker connection

String   HVACStatus        "Hvac mode"         {channel="mqtt:topic:MQTTBroker:device_1:status"}
Number   HVACTemperature   "Set temperature"   {channel="mqtt:topic:MQTTBroker:device_1:temperature"}
String   HVACType          "Label"             {channel="mqtt:topic:MQTTBroker:device_1:type"}

The next step would be to figure out what you need to send to MQTT to get the device to change state.

Thanks for the extra examples. Yesterday I figured it out how to to this via the (string) channels. But sometimes the linked items got the value “NULL”. This was caused because not all MQTT JSON strings contains always all devices. This was solved after I added

allowedStates = “True,False”

to the thing.

@rossko57 and @denominator thank you both to get me in the right direction!

To handle this you can filter out the messages in the channel that don’t have the device for that channel so you don’t get JSON error in logs. Install the Regex transformation


transformationPattern = "REGEX:(.*Device_1.*)∩JSONPATH:$.Params[*].Devices[?(@.ID == 'Device_1')].Properties[*].Status",

The regex is asking if you find the string Device_1 anywhere in the string pass it to JSONPATH if not ignore.

∩ = Intersection not a N or n

1 Like

Thanks, great tip and working good!