Transformation of nested JSON in rules via JSONPath

Hello everyone,

I recently did quite a jump on my system by impletenting a rules following the design pattern made by @skatun (see Magical light scenes). However, I was not quite pleased with the way some of the things are handled there. I did at first go with the Map approach to transform different states, but since I want to my scenes to include not only the lights them selves being turned on, but also passed a brightness and a color, I am trying a new approach.

Instead of using three .map files like the following. The number represents the switch state in all files.

// livingroom_scene.map
0=OFF
1=ON
2=morning
// livingroom_brightness.map
0=0
1=100
2=80
// livingroom_color.map
0=0
1=40
2=100

I was thinking of using a .json file, in order to be able to easily set scenes and also tell the values apart:

{"lr":
{"0": {"mood": "OFF", "brightness": "0", "color": "40"},
"1": {"mood": "ON", "brightness": "100", "color": "0"},
"2": {"mood": "night", "brightness": "50", "color": "100"},
"3": {"mood": "morgen", "brightness": "100", "color": "400"},
"4": {"mood": "arbeit", "brightness": "100", "color": "0"},
"5": {"mood": "dinner", "brightness": "70", "color": "40"},
"6": {"mood": "tv", "brightness": "50", "color": "40"}
}}

While this at first seemed like a rather smart approach, I now can’t process the file.

In my rule I am trying to set value for the brightness like so.

val strHelligkeit = transform("JSONPATH", "$." + roomName + "." + triggeringItem.state , "" + triggeringItem.state)

roomName in this scenario being lr and triggeringItem.state being the scene number. strHelligkeit is German for strBrightness, but what kind of lunatic would stick one scheme when namig variables. I guess that will not work, because I can’t clarify where to search for triggeringItem.state within the .json. logInfo("Brightness", strHelligkeit) always returns the State of the triggering item, rather then an actually transformed value.

Is there a way to achieve my setup in this kind of way or should I stick to creating multiple .map files?

I’m not sure what you think you’re doing in the transform, but you are searching for JSON in your triggering Items state. Didn’t you mean to search in your JSON file contents? You’d have to import that as a string first.

I guess I wasnt being clear then. I am not sure if the transform function will search for the given triggeringItem.state at the right position, which would be $..triggeringItem.. It then might return the whole second bracket, when I would just need the .brightness Part.

Actually I’m not even sure anymore if there is a transform needed. \

What I want for the transform to do is give out the brightness for the selected Input. In the above example I expected it to return 100 for triggeringItem.state=3, what it instead returns is 3.

Since I might have gotten something wrong in the first place, I have now changed my transform to

val strHelligkeit = transform("JSONPATH", "$." + roomName + "." + triggeringItem.state, ".brightness")

in an attempt to return the right thing, it still just returns the input, in this case ".brightness".

I don’t think you’ve understood the syntax of transform().

val banana = transform( stringA , stringB , stringC )

stringA is what to do, what transform to apply. In your case, “JSONPATH”

stringB is what to do it with, the transform parameter. In your case, something like “$.blah.bleh”.

stringC is what to do it to, the transform target. In your case, “{“lr”:
{“0”: {“mood”: “OFF”, “brightness”: “0”, “color”: “40”}, “1”: {“mood”: …”

So …

will search for JSON path based on your Item state in the string “.brightness”.
The string “.brightness” is not valid JSON, so this will fail.
When a transform fails, you get the original data untransformed (a maybe unexpected feature of openHAB transformations).

You haven’t plugged your source JSON data in as the thing to be searched through.

Example

val rawjson = '{"lr":
{"0": {"mood": "OFF", "brightness": "0", "color": "40"},
"1": {"mood": "ON", "brightness": "100", "color": "0"},
"2": {"mood": "night", "brightness": "50", "color": "100"},
"3": {"mood": "morgen", "brightness": "100", "color": "400"},
"4": {"mood": "arbeit", "brightness": "100", "color": "0"},
"5": {"mood": "dinner", "brightness": "70", "color": "40"},
"6": {"mood": "tv", "brightness": "50", "color": "40"}
}}
'
var results = transform("JSONPATH", "$.lr.1.brightness", rawjson)
logInfo("test", " simple path " + results)

val roomName = "lr"
val sceneNumber = "1"  // or in your case, your Item state as string
results = transform("JSONPATH", "$." + roomName + "." + sceneNumber + ".brightness", rawjson)
logInfo("test", " compound path " + results)

results

2021-08-10 20:55:37.293 [INFO ] [.eclipse.smarthome.model.script.test] -  simple path 100
2021-08-10 20:55:37.295 [INFO ] [.eclipse.smarthome.model.script.test] -  compound path 100

Don’t forget your result is always a string, even if it looks like a number.

1 Like

Okay, so that is the problem there. I have now changed the transform Command to

val strHelligkeit = transform("JSONPATH", "$." + roomName + "." + triggeringItem.state.toString + ".brightness", json)

With json being a val String json = '<example from above>'. This works just fine, so thank you very much!

Is there a way to implement the .json as a local file, like from /etc/openhab/transform/items.json or should I just leave it within the rule?

Sure. You’ll need to read the file into astring variable, which is the fiddly bit and depends on your host OS.
Example -

1 Like

Well, your jsonpath is wrong.

val String strRoomName   = "lr"
val String strMood       = "morgen"
val String strJSONpath   = "$." + strRoomName + ".[?(@.mood=='" + strMood + "')]."
val String strHelligkeit = transform("JSONPATH", strJSONpath + "brightness", rawjson)
val String strColor      = transform("JSONPATH", strJSONpath + "color", rawjson)

I’ve split the interesting part to the val strJSONpath, the “real” thing is the part between [ and ].
The resulting string is

"$.lr.[?(@.mood=='morgen')]."

so the final jsonpath is

"$.lr.[?(@.mood=='morgen')].brightness"

which will result in string “100”.

I don’t quite understand your answer here. With the given json and @rossko57’s solution it works just fine. I how your solution would work, but aren’t that just some extra steps for the same result?

I kinda like the JSON path to do things, I might go over and revise my template once I upgrade to OH3.

@skatun I feel beyond happy to be able to give something back to you after your rules basically got me started. Took me some time, but now I’m good to go and adjust every thin. Thank you so much for that tutorial!

Sorry, misunderstood your first posting.