How to convert Java Map to a basic Javascript object?
Asked Answered
P

4

16

I'm starting to use the dynamic rhinoscript feature in Java 6 for use by customers who are more likely to know Javascript than Java.

What is the best way to pass a Map (associative array, javascript obj, whatever) into Javascript so the script-writers can use the standard Javascript dot notation for accessing values?

I'm currently passing a java.util.Map of values into the script, however then the script writer has to write "map.get('mykey')" instead of "map.mykey".

Basically, I want to do the opposite of this question.

Paphian answered 22/9, 2011 at 18:7 Comment(0)
P
12

I took the Java NativeObject approach and here is what I did...

// build a Map
Map<String, String> map = new HashMap<String, String>();
map.put("bye", "now");

// Convert it to a NativeObject (yes, this could have been done directly)
NativeObject nobj = new NativeObject();
for (Map.Entry<String, String> entry : map.entrySet()) {
    nobj.defineProperty(entry.getKey(), entry.getValue(), NativeObject.READONLY);
}

// Get Engine and place native object into the context
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("javascript");
engine.put("map", nobj);

// Standard Javascript dot notation prints 'now' (as it should!)
engine.eval("println(map.bye);");
Paphian answered 27/9, 2011 at 21:17 Comment(2)
This does not seem to work. It writes "undefined". I still need to access the property with the get method: map.get('bye').Wellknown
See also #37358031Azores
E
7

I am using an utility class that convert Map into javascript hash object:

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.mozilla.javascript.Scriptable;
public class MapScriptable implements Scriptable, Map {
    public final Map map;
    public MapScriptable(Map map) {
        this.map = map;
    }
    public void clear() {
        map.clear();
    }
    public boolean containsKey(Object key) {
        return map.containsKey(key);
    }
    public boolean containsValue(Object value) {
        return map.containsValue(value);
    }
    public Set entrySet() {
        return map.entrySet();
    }
    public boolean equals(Object o) {
        return map.equals(o);
    }
    public Object get(Object key) {
        return map.get(key);
    }
    public int hashCode() {
        return map.hashCode();
    }
    public boolean isEmpty() {
        return map.isEmpty();
    }
    public Set keySet() {
        return map.keySet();
    }
    public Object put(Object key, Object value) {
        return map.put(key, value);
    }
    public void putAll(Map m) {
        map.putAll(m);
    }
    public Object remove(Object key) {
        return map.remove(key);
    }
    public int size() {
        return map.size();
    }
    public Collection values() {
        return map.values();
    }
    @Override
    public void delete(String name) {
        map.remove(name);
    }
    @Override
    public void delete(int index) {
        map.remove(index);
    }
    @Override
    public Object get(String name, Scriptable start) {
        return map.get(name);
    }
    @Override
    public Object get(int index, Scriptable start) {
        return map.get(index);
    }
    @Override
    public String getClassName() {
        return map.getClass().getName();
    }
    @Override
    public Object getDefaultValue(Class<?> hint) {
        return toString();
    }
    @Override
    public Object[] getIds() {
        Object[] res=new Object[map.size()];
        int i=0;
        for (Object k:map.keySet()) {
            res[i]=k;
            i++;
        }
        return res;
    }
    @Override
    public Scriptable getParentScope() {
        return null;
    }
    @Override
    public Scriptable getPrototype() {
        return null;
    }
    @Override
    public boolean has(String name, Scriptable start) {
        return map.containsKey(name);
    }
    @Override
    public boolean has(int index, Scriptable start) {
        return map.containsKey(index);
    }
    @Override
    public boolean hasInstance(Scriptable instance) {
        return false;
    }
    @Override
    public void put(String name, Scriptable start, Object value) {
        map.put(name, value);
    }
    @Override
    public void put(int index, Scriptable start, Object value) {
        map.put(index, value);
    }
    @Override
    public void setParentScope(Scriptable parent) {}
    @Override
    public void setPrototype(Scriptable prototype) {}
}

Sample:

import java.util.HashMap;
import java.util.Map;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ScriptableObject;

public class MapScriptableMain {
    public static void main(String[] args) {
        Map src=new HashMap();
        src.put("foo", 2);
        src.put("bar", 3);
        MapScriptable m=new MapScriptable(src);
        Context c=Context.enter();
        ScriptableObject scope = c.initStandardObjects();
        ScriptableObject.putProperty(scope, "m", m);
        String source = "m.baz=m.foo+m.bar;";
        Object a=c.evaluateString(scope, source, "TEST", 1, null);
        System.out.println(a); // 5.0
        System.out.println(src.get("baz")); // 5.0;
    }
}
Exhort answered 30/9, 2011 at 3:58 Comment(0)
E
3

After figuring out that the SimpleScriptContext will only take the Map object and thus force you to use the Java methods in your JavaScript, here's what I did.

Map<String, String> myMap = new HashMap<String, String>();
myMap.put("test", "hello world!");

ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
Object eval = engine.eval("var map = " + new Gson().toJson(myMap) + ";\n" 
    + "println(map.test);");

Which printed out

hello world!
Erotogenic answered 26/11, 2012 at 14:41 Comment(1)
I did it this way also but using Jackson. I'm slightly worried about performance now as the script will always be changing. In theory one could use Jackson's Object Mapping capabilities combined with the NativeObject (@JonCarlson) support to alleviate the performance concerns. The other option is writting a generic javascript that recursively walks hashmaps.Render
M
2

You just need to encode your object as JSON, either manually, or using a library like Jackson or gson. As you said, it's the exact oposite of that question and the author of that question is not happy with the JSON notation :)

What you need to send to the browser is basically something like this:

var someObject = { "key1": "value1", "key2": "value2", ... }

And then the javascript developer can simply access: someObject.key2.

Mockingbird answered 22/9, 2011 at 18:13 Comment(1)
Thanks for your quick response! That works great when sending JSON to a browser, but it doesn't work so well on the server. Here's my example: ` ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName("javascript"); engine.put("obj", "{ \"key1\": \"value1\", \"key2\": \"value2\"}"); // This returns "string" instead of "obj". I don't want to make // the script writer eval the string, although I may have to. engine.eval("typeof(obj);"); 'Paphian

© 2022 - 2024 — McMap. All rights reserved.