Is there a function out there that will turn a json string or python dict into ObjectWrapper?
If I take a python dict and assign it to a property on a perspective view it turn the property into a object. It I then go and get the property in script the property does not come back as a dictionary it comes back as an ObjectWrapper. What I want to do in script is go from a dictionary to an object wrapper without having to assing it to a view property.
We have objects that we serialized and stored on the views. Then later when we need that object we can deserialize the ObjectWrapper.
The problem now is I want to store the serialized object in a string tag, but when I pull it back out and try the existing deserializing it does not work because I cannot reference the string like the ObjectWrapper. I also tried using system.util.jsonDecode(), but that turns it into a dictionary and I cannot reference the properties the same way
e.g.
object.property1
object.property2
If you had a function to turn an ObjectWrapper into a python dictionary I could probably make that work with out to much refactoring on our side.
I would then have to change all our deserialize functions to reference properties like this
You should be able to dict(<objectWrapper>) within a release or two; I opened a PR for that last week, just needs QA sign off. That’s probably going to be the sanest way forward; you most likely won’t have any success trying to manually convert an object into one of the wrapper types.
I’d also highly recommend migrating towards the __getitem__/braces syntax, over attribute retrieval. We make that work to make things nicer for anyone more used to working with JS objects, but it’s got limitations from a Pythonic perspective.
Actually, what’s the full str(type(<wrapper>))? If it’s a PropertyTreeScriptWrapper$ObjectWrapper I would expect dict() to work right now, assuming you’re on 8.0.5+.
@PGriffith
I’m trying to do this as well. I have a custom param that’s an Object with this format:
When I use dict() to convert to a dict, the base controlAccess works fine, but its key values are still ObjectWrappers. I assume i’m going to have to loop through it and convert all ObjectWrappers into dicts?
I’ve just tried this and I can’t import the object: import com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper
With the error: ImportError: No module named perspective
v8.1.5
This works, but yuck! str(type(dict(self.view.custom.controlAccess)['racking'])) == "<type 'com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper$ObjectWrapper'>"
Ah, so it is an ObjectWrapper...
Interesting. I'm actually not sure whether we can 'fix' that; it'd be nice if dict was automatically recursive, but c'est la vie. We'd have to introduce a new method, which sounds pretty gross...
You could make your deep to-dict function not care about specific types by relying on the presence of Python magic methods, as in this post:
Cool, that looks useful
What I’m trying to do is to make a copy of that controlAccess object, manipulate it, then write it back. What I found was that if I just assigned a variable to it e.g. controlAccess = self.custom.controlAccess, the controlAccess python variable became a reference to the View’s custom property, which I thought was a bit strange.
Is there a better way to do this, rather than converting things all to dicts? can I make a copy like dict’s copy() method of an ObjectWrapper object?
Well, dict.copy() is just a shallow copy - it’s basically doing exactly what dict(<some dict>) would do. A less performant, but maybe easier solution would be to json.loads(json.dumps(self.custom.controlAccess)) - that’ll be an easy deep-copy. (The same technique is common in the front-end world).
Oh, right. The builtin JSON function is a little weird. Either of these seem to work:
import json
from java.util import Date
class IgnitionEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Date):
return obj.time
elif hasattr(obj, "keys"):
return dict(obj)
elif hasattr(obj, "__iter__"):
return list(obj)
return json.JSONEncoder.default(self, obj)
j = json.dumps(self.custom.controlAccess, cls=IgnitionEncoder, indent=4)
system.perspective.print(j)
from com.inductiveautomation.ignition.common import TypeUtilities
system.perspective.print(TypeUtilities.gsonToPy(TypeUtilities.pyToGson(self.custom.controlAccess)))
Forcing each to the expected type while traversing makes a deep copy:
def sanitize_tree(element):
if hasattr(element, '__iter__'):
if hasattr(element, 'keys'):
return dict((k, sanitize_tree(element[k])) for k in element.keys())
else:
return list(sanitize_tree(x) for x in element)
return element
EDIT: Got rid of the unnecessary zip() and used only one keys() iterator instead of two.
There seems to be a limitation on the number of nested dictionaries with this function, unless I'm using it wrong?
e.g.
def sanitize_tree(element):
'''Take a Perspective ObjectWrapper Tree and convert to python dict or list'''
if hasattr(element, '__iter__'):
if hasattr(element, 'keys'):
return dict((k, sanitize_tree(element[k])) for k in element.keys())
else:
return list(sanitize_tree(x) for x in element)
return element
top= {'top':{}}
print sanitize_tree(top)
inner1 = {'top': {'inner1': {}}}
print sanitize_tree(inner1)
inner2 = {'top': {'inner1': {'inner2':'test'}}}
print sanitize_tree(inner2)
result
{'top': {}}
{'top': {'inner1': {}}}
Traceback (most recent call last):
File "<input>", line 37, in <module>
File "<input>", line 25, in sanitize_tree
...
RuntimeError: maximum recursion depth exceeded (Java StackOverflowError)