I am trying to check a diff between a previous tag value and current tag value in event streams and I'm running into issues with the equality semantics. I am pointing the tag source to an aggregation of tags via jsonvalues to be sent to external systems. I essentially need to filter out the aggregates that only have noisy tag changes like a heartbeat tag.
Without encoding and decoding from json (performance reasons) or recursively checking each field, is there a way to get a proper equality comparison? I'm hoping I am just missing something.
Example script:
from com.inductiveautomation.ignition.common.gson import JsonObject
from com.inductiveautomation.ignition.common.script.adapters import PyJsonObjectAdapter
# Assuming this is the correct way to construct PyJsonObjectAdapter to mimic Event Stream object type
jsonObject1 = JsonObject()
jsonObject1.addProperty("State", "Running")
jsonObject1.addProperty("Mode", "Auto")
jsonObject2 = JsonObject()
jsonObject2.addProperty("State", "Running")
jsonObject2.addProperty("Mode", "Auto")
value1 = PyJsonObjectAdapter(jsonObject1)
value2 = PyJsonObjectAdapter(jsonObject2)
print "value1 type: {}".format(type(value1))
print "value2 type: {}".format(type(value2))
print "value1: {}".format(value1)
print "value2: {}".format(value2)
# Expected false
print "Same object: {}".format(value1 is value2)
# Expected true based on contents. Seems as though it is falling back to identity comparison
print "'Python' equality: {}".format(value1 == value2)
print "---"
print "Field comparison:"
print "State equal: {}".format(value1["State"] == value2["State"])
print "Mode equal: {}".format(value1["Mode"] == value2["Mode"])
Output:
value1 type: <type 'com.inductiveautomation.ignition.common.script.adapters.PyJsonObjectAdapter'>
value2 type: <type 'com.inductiveautomation.ignition.common.script.adapters.PyJsonObjectAdapter'>
value1: {"State":"Running","Mode":"Auto"}
value2: {"State":"Running","Mode":"Auto"}
Same object: 0
'Python' equality: 0
---
Field comparison:
State equal: 1
Mode equal: 1
Javadoc reference:
EDIT:
Found the implementation for __eq__ that PyJsonObjectAdapter inherits from.
How else could a structural equality comparison work?
If you want deep equals, you have to do the recursive traversal.
If you can express your algorithm more cheaply or via a shortcut (e.g. only compare equality on keys) it's easy enough to write that out yourself.
In theory, if you can get the underlying JsonObject out of the adapter, you can use the equals method on it directly, which at least saves you writing the recursion yourself.
Huh, that's clever. I would not have thought to do that.
As far as I can tell that only works through some fairly impressive Jython machinery; it's calling the instance method of JsonObject as if it were static because you passed in an instance to use as self, and the first instance you passed only works because it's automatically "unwrapping" the object adapter.
Okay, just upgraded from 8.3.7 to 8.3.10 and the assumption that event.data.value and event.metadata.previousValue.value are both PyJsonObjectAdapter is not true anymore
8.3.7 Behavior:
event.data.value is a PyJsonObjectAdapter
event.metadata.previousValue.value is a PyJsonObjectAdapter
8.3.10 Behavior:
event.data.value is a PyJsonObjectAdapter
event.metadata.previousValue.value is a java.util.HashMap
event.metadata.previousValue can no longer reference value in dot notation but rather you have to use the key lookup i.e. ["somekey"].
Workaround:
Use key lookup for previousValue
Cast event.data.value to a HashMap to do a comparison
My filtering code is completely broken on the dev server that I upgraded while the production servers that are on 8.3.7 are still properly filtering. This looks like a regression to me or undocumented behavior change. I will open a ticket with support to make sure.
It was 8.3.8, under an umbrella of changes to improve event stream performance. It'll be stable going forward - the change was justified under the logic that previously metadata was mutable which was fully incorrect.