Converting Python objects to JavaScript for PyV8

不羁的心 提交于 2019-12-07 12:04:17

问题


I'm trying to pass Python data (lists, dicts, strings..., arbitrarily nested) to PyV8.

class Global(object):
    def __init__(self, data):
        self.data = data
ctx = PyV8.JSContext(Global([{'a':1}]))
ctx.enter()
res = ctx.eval('data.length')
js_len = PyV8.convert(res)
print js_len

The code above prints None, presumably because the data object is not transformed to a JSArray and thus data.length evaluates to undefined. Is there a reliable way to do the necessary conversion in PyV8 other than using JSON?


回答1:


Apparently PyV8 doesn't correctly convert python lists to Javascript arrays, which leads my_list.length to return undefined, which is getting converted to None.

ctx = PyV8.JSContext()
ctx.enter()
ctx.locals.a = [{'a':1}]
print ctx.locals.a
#> [{'a': 1}]
print ctx.eval("a.length")
#> None
print ctx.eval("a[0].a")
#> 1
ctx.locals.blub = {'a':1}
print ctx.eval("blub.a")
#> 1
print ctx.eval("Object.keys(blub)")
#> a
ctx.locals.blub = {'a':[1,2,3]}
print ctx.eval("Object.keys(blub)")
#> a
print ctx.eval("blub.a")
#> [1, 2, 3]
ctx.locals.blub2 = [{'a':[1,2,3]}]
print ctx.eval("blub2")
#> [{'a': [1, 2, 3]}]
print ctx.eval("blub2.length")
#> None
print ctx.eval("Array.isArray(blub2)")
#> False
print ctx.eval("typeof(blub2)")
#> object
print ctx.eval("blub2[0].a")
#> [1, 2, 3]
print ctx.eval("typeof(blub.a)")
#> object
print ctx.eval("Array.isArray(blub.a)")
#> False

The answer is to use PyV8.JSArray(my_list). I've written the following helper functions for my project that deal with various little problems and make it easy to convert back and forth between python and js objects. These are targeted at a specific version of PyV8 however (which is the only version I can recommend, see discussion in the linked issues), so your results may vary if you use them as-is. Example usage:

ctx.locals.blub3 = get_js_obj({'a':[1,2,3]})
ctx.locals.blub4 = get_js_obj([1,2,3])
ctx.eval("blub3.a.length")
#> 3
ctx.eval("blub4.length")
#> 3

And here are the functions.

def access_with_js(ctx, route):
    if len(route) == 0:
        raise Exception("route must have at least one element")
    accessor_string = route[0]
    for elem in route[1:]:
        if type(elem) in [str, unicode]:
            accessor_string += "['" + elem + "']"
        elif type(elem) == int:
            accessor_string += "[" + str(elem) + "]"
        else:
            raise Exception("invalid element in route, must be text or number")
    return ctx.eval(accessor_string)

def get_py_obj(ctx, obj, route=[]):
    def dict_is_empty(dict):
        for key in dict:
            return False
        return True

    def access(obj, key):
        if key in obj:
            return obj[key]
        return None

    cloned = None
    if isinstance(obj, list) or isinstance(obj, PyV8.JSArray):
        cloned = []
        temp = str(access_with_js(ctx, route)) #working around a problem with PyV8 r429
        num_elements = len(obj)
        for index in range(num_elements):
            elem = obj[index]
            cloned.append(get_py_obj(ctx, elem, route + [index]))
    elif isinstance(obj, dict) or isinstance(obj, PyV8.JSObject):
        cloned = {}
        for key in obj.keys():
            cloned_val = None
            if type(key) == int:
                #workaround for a problem with PyV8 where it won't let me access
                #objects with integer accessors
                val = None
                try:
                    val = access(obj, str(key))
                except KeyError:
                    pass
                if val == None:
                    val = access(obj, key)
                cloned_val = get_py_obj(ctx, val, route + [key])
            else:
                cloned_val = get_py_obj(ctx, access(obj, key), route + [key])
            cloned[key] = cloned_val
    elif type(obj) == str:
        cloned = obj.decode('utf-8')
    else:
        cloned = obj
    return cloned

def get_js_obj(ctx,obj):
    #workaround for a problem with PyV8 where it will implicitely convert python lists to js objects
    #-> we need to explicitely do the conversion. see also the wrapper classes for JSContext above.
    if isinstance(obj, list):
        js_list = []
        for entry in obj:
            js_list.append(get_js_obj(ctx,entry))
        return PyV8.JSArray(js_list)
    elif isinstance(obj, dict):
        js_obj = ctx.eval("new Object();") # PyV8.JSObject cannot be instantiated from Python
        for key in obj.keys():

            try:
                js_obj[key] = get_js_obj(ctx,obj[key])
            except Exception, e:
                # unicode keys raise a Boost.Python.ArgumentError 
                # which can't be caught directly:
                # https://mail.python.org/pipermail/cplusplus-sig/2010-April/015470.html
                if (not str(e).startswith("Python argument types in")):
                    raise
                import unicodedata
                js_obj[unicodedata.normalize('NFKD', key).encode('ascii','ignore')] = get_js_obj(ctx,obj[key])
        return js_obj
    else:
        return obj


来源:https://stackoverflow.com/questions/28642999/converting-python-objects-to-javascript-for-pyv8

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!