如何使用 Nashorn 的 JSObject 进行字符串连接

How to make string concatenation work with Nashorn's JSObject

意图

我正在使用 Java 8u60(不是 8u51,这很重要!)并且正在使用它的 Nashorn Java脚本引擎。我通过扩展 AbstractJSObject 创建了自己的 JSObject。它应该包装一个 org.json.JSONObject 并使其像脚本引擎中的实际 JavaScript 对象一样工作。鉴于 javaAPI 是一个 Java 对象,它被放入 ScriptContext,生成的对象应该像这样可用:

var jsonObject = javaAPI.doSomethingThatReturnsAJSONObject();
var foo = jsonObject.foo
jsonObject.foo = "bar";
delete jsonObject.foo;
var message = "JSON: " + jsonObject;

代码

public class JSONObjectJavaScriptAdapter extends AbstractJSObject {

    private final JSONObject jsonObject;

    public JSONObjectJavaScriptAdapter(final JSONObject jsonObject) {
        this.jsonObject = jsonObject;
    }

    @Override
    public void removeMember(String name) {
        jsonObject.remove(name);
    }

    @Override
    public void setMember(String name, Object value) {
        jsonObject.put(name, value);
    }

    @Override
    public Set<String> keySet() {
        return jsonObject.keySet();
    }

    @Override
    public boolean hasMember(String name) {
        return jsonObject.has(name);
    }

    @Override
    public Object getMember(String name) {
        return jsonObject.get(name);
    }

    @Override
    public String toString() {
        return jsonObject.toString();
    }
}

问题

除了字符串连接外,一切正常。写类似

var message = "JSON: " + jsonObject;

将导致以下异常:

org.json.JSONException: JSONObject["valueOf"] not found.
    at org.json.JSONObject.get(JSONObject.java:476)
    at my.JSONObjectJavaScriptAdapter.getMember(JSONObjectJavaScriptAdapter.java:50)
    at jdk.nashorn.api.scripting.DefaultValueImpl.getDefaultValue(DefaultValueImpl.java:42)
    at jdk.nashorn.api.scripting.AbstractJSObject.getDefaultValue(AbstractJSObject.java:269)
    at jdk.nashorn.api.scripting.AbstractJSObject.getDefaultValue(AbstractJSObject.java:285)
    at jdk.nashorn.internal.runtime.JSType.toPrimitive(JSType.java:512)
    at jdk.nashorn.internal.runtime.JSType.toPrimitive(JSType.java:480)
    at jdk.nashorn.internal.runtime.JSType.toPrimitive(JSType.java:462)
    at jdk.nashorn.internal.runtime.ScriptRuntime.ADD(ScriptRuntime.java:563)
    at jdk.nashorn.internal.scripts.Script$Recompilation$configuration.main(src/test/resources/de/ams/inm/workflow/engine/javascript/async/configuration.js:23)
    at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:640)
    at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:228)
    at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:393)
    at jdk.nashorn.api.scripting.ScriptObjectMirror.callMember(ScriptObjectMirror.java:199)
    at jdk.nashorn.api.scripting.NashornScriptEngine.invokeImpl(NashornScriptEngine.java:383)
    at jdk.nashorn.api.scripting.NashornScriptEngine.invokeFunction(NashornScriptEngine.java:190)
    [...]

我需要做什么才能使 Nashorn 调用 toString() 方法,以便 message 包含类似 JSON: {"foo":"bar} 的内容?

解决方案

事实证明,Nashorn 严格遵守 ECMA 规范。重要的部分是The Addition operator ( + ) and [[DefaultValue]].

如果 + 运算符用于对象,这些对象将使用 [[DefaultValue]] 函数转换为基元。默认的 [[DefaultValue]] 实现通过使用 valueOftoString 函数将对象转换为基元。

对象可以重写 [[DefaultValue]] 函数以提供自定义到基元的转换。由于 Java 8u60 这也可能在 Nashorn 中,通过覆盖 AbstractJSObject.getDefaultValue(Class)

我将以下代码添加到 JSONObjectJavaScriptAdapter 以使字符串连接正常工作:

@Override
public Object getDefaultValue(Class<?> hint) {
    return toString();
}

此外,如果 JSObject.getMember 可以处理任何抛出的异常,也许 return 为 null 或适当的值 - 而不是传播异常。在您的示例中,org.json.JSONObject.get 在未找到 属性 时抛出异常。

另一件事:您可以 return 'function value' 用于 'toString' 或 'valueOf' 属性 - 这样它就可以用于 toString/toNumber ECMAScript 中指定的转换。