如何使用 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]] 实现通过使用 valueOf
或 toString
函数将对象转换为基元。
对象可以重写 [[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 中指定的转换。
意图
我正在使用 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]] 实现通过使用 valueOf
或 toString
函数将对象转换为基元。
对象可以重写 [[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 中指定的转换。