Skip to content

Commit 21acfc5

Browse files
author
Stefan Anzinger
committed
[GR-26744] JSON.stringify() should try to use toJSON() of foreign objects.
PullRequest: js/1721
2 parents ed83f3e + 64777a3 commit 21acfc5

File tree

2 files changed

+41
-6
lines changed

2 files changed

+41
-6
lines changed

graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/interop/JSONStringifyInteropTest.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
import org.graalvm.polyglot.Context;
5252
import org.graalvm.polyglot.HostAccess;
5353
import org.graalvm.polyglot.Value;
54+
import org.graalvm.polyglot.proxy.ProxyExecutable;
55+
import org.graalvm.polyglot.proxy.ProxyObject;
5456
import org.junit.Test;
5557

5658
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
@@ -85,12 +87,38 @@ public void testForeignExecutable() {
8587
}
8688
}
8789

90+
private static void checkStringification(Object objectToStringify, String expectedResult) {
91+
try (Context context = JSTest.newContextBuilder().allowHostAccess(HostAccess.ALL).build()) {
92+
context.getBindings(ID).putMember("objectToStringify", objectToStringify);
93+
Value result = context.eval(ID, "JSON.stringify(objectToStringify)");
94+
assertEquals(expectedResult, result.asString());
95+
}
96+
}
97+
8898
@Test
8999
public void testNonReadableMembers() {
90-
try (Context context = JSTest.newContextBuilder().allowHostAccess(HostAccess.ALL).build()) {
91-
context.getBindings(ID).putMember("myObj", new InvocableMemberObject(Collections.singletonMap("someKey", "someValue")));
92-
Value result = context.eval(ID, "JSON.stringify(myObj)");
93-
assertEquals("{}", result.asString());
100+
checkStringification(new InvocableMemberObject(Collections.singletonMap("someKey", "someValue")), "{}");
101+
}
102+
103+
@Test
104+
public void testToJSONOfProxyObject() {
105+
Object object = ProxyObject.fromMap(Collections.singletonMap("toJSON", new ProxyExecutable() {
106+
@Override
107+
public Object execute(Value... arguments) {
108+
return "fromToJSON";
109+
}
110+
}));
111+
checkStringification(object, "\"fromToJSON\"");
112+
}
113+
114+
@Test
115+
public void testToJSONOfHostObject() {
116+
checkStringification(new HostObjectWithToJSON(), "\"HostObjectWithToJSON\"");
117+
}
118+
119+
public static class HostObjectWithToJSON {
120+
public Object toJSON(@SuppressWarnings("unused") String key) {
121+
return "HostObjectWithToJSON";
94122
}
95123
}
96124

graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/helper/JSONStringifyStringNode.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,14 @@ private Object jsonStrPrepareForeign(JSONData data, int key, Object holder) {
199199

200200
private Object jsonStrPreparePart2(JSONData data, String key, Object holder, Object valueArg) {
201201
Object value = valueArg;
202+
boolean tryToJSON = false;
202203
if (JSRuntime.isObject(value) || JSRuntime.isBigInt(value)) {
204+
tryToJSON = true;
205+
} else if (JSRuntime.isForeignObject(value)) {
206+
InteropLibrary interop = InteropLibrary.getUncached(value);
207+
tryToJSON = interop.hasMembers(value) && !interop.isNull(value) && !interop.isBoolean(value) && !interop.isString(value) && !interop.isNumber(value);
208+
}
209+
if (tryToJSON) {
203210
value = jsonStrPrepareObject(key, value);
204211
}
205212

@@ -241,12 +248,12 @@ private Object jsonStrPrepareObject(Object key, Object value) {
241248
}
242249
Object toJSON = getToJSONProperty.getValue(value);
243250
if (JSRuntime.isCallable(toJSON)) {
244-
return jsonStrPrepareObjectFunction(key, value, (DynamicObject) toJSON);
251+
return jsonStrPrepareObjectFunction(key, value, toJSON);
245252
}
246253
return value;
247254
}
248255

249-
private Object jsonStrPrepareObjectFunction(Object key, Object value, DynamicObject toJSON) {
256+
private Object jsonStrPrepareObjectFunction(Object key, Object value, Object toJSON) {
250257
if (callToJSONFunction == null) {
251258
CompilerDirectives.transferToInterpreterAndInvalidate();
252259
callToJSONFunction = insert(JSFunctionCallNode.createCall());

0 commit comments

Comments
 (0)