Function to convert json string or python dict into ObjectWrapper

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.

Why?
What’s your end goal, if you create an ObjectWrapper?

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

object[“property1”]
object[“property2”]

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.

Great thanks. I did not know you could use the brace syntax on the view propriety objects. That may just fix my issue.

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+.

yes I am on 8.0.7

com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper$ObjectWrapper

@PGriffith
I’m trying to do this as well. I have a custom param that’s an Object with this format:
image

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'>"

What’s your exact script? Doing the obvious thing seems to work for me:

Can you print out:
type(dict(self.custom.controlAccess)['racking'])?

1 Like

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 :slight_smile:
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).

No dice :frowning:

 File "C:\Program Files\Inductive Automation\Ignition\user-lib\pylib\json\__init__.py", line 243, in dumps
    return _default_encoder.encode(obj)
  File "C:\Program Files\Inductive Automation\Ignition\user-lib\pylib\json\encoder.py", line 206, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "C:\Program Files\Inductive Automation\Ignition\user-lib\pylib\json\encoder.py", line 269, in iterencode
    return _iterencode(o, 0)
  File "C:\Program Files\Inductive Automation\Ignition\user-lib\pylib\json\encoder.py", line 183, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: {u'vent': {u'enabled': False}, u'sump': {u'enabled': False}, u'scanTimeStamp': None, u'gas': {u'enabled': False}, u'racking': {u'enabled': False}} is not JSON serializable

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)))
4 Likes

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.

11 Likes

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)

That usually happens when an inner element points at one of its parents.

1 Like

Whitespace errors... Most of my sample is space-indented, but you have a mix of tab-indents. Go double-check...