GraalSDK中Value.as(Map.class)未将JS数组转为List导致测试失败的问题排查
Let's break down why your test is failing, and how to fix it.
The core issue here is that you're using the raw Map.class type when calling result.as(Map.class). While the GraalVM docs mention that recursive type mapping should apply, that rule only kicks in when you provide a parameterized generic type (not a raw class) so GraalVM knows exactly what type you expect for the map's values.
When you pass Map.class (a raw type), GraalVM has no way to infer that you want array-like values to be converted to List<Object>—instead, it treats JS arrays (which are technically objects under the hood) as generic polyglot objects, wrapping them in PolyglotMap instead of a proper List.
The Fix: Use a Parameterized Type for Your Map
To trigger the recursive type mapping behavior described in the docs, you need to tell GraalVM you want a Map<String, Object> specifically, not just a raw Map. Here are two common ways to do this:
Option 1: Use Guava's TypeToken (Simpler)
If you're already using Guava, this is the easiest approach:
import com.google.common.reflect.TypeToken; // ... @Test public void testList() { try (Context context = Context.create()) { Value value = context.eval("js", JS_CODE); Value result = value.execute(); // Use TypeToken to specify the parameterized Map type Type mapType = new TypeToken<Map<String, Object>>() {}.getType(); Map<String, Object> resultMap = result.as(mapType); assertThat(resultMap).hasEntrySatisfying("listProperty", testArray -> { assertThat(testArray).isInstanceOf(List.class) .asList().containsExactly("listValue"); }); } }
Option 2: Manually Construct a ParameterizedType (No Dependencies)
If you don't want to add Guava, you can construct the parameterized type yourself using reflection:
import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; // ... @Test public void testList() { try (Context context = Context.create()) { Value value = context.eval("js", JS_CODE); Value result = value.execute(); // Create a ParameterizedType representing Map<String, Object> Type mapType = new ParameterizedType() { @Override public Type[] getActualTypeArguments() { return new Type[]{String.class, Object.class}; } @Override public Type getRawType() { return Map.class; } @Override public Type getOwnerType() { return null; } }; Map<String, Object> resultMap = result.as(mapType); assertThat(resultMap).hasEntrySatisfying("listProperty", testArray -> { assertThat(testArray).isInstanceOf(List.class) .asList().containsExactly("listValue"); }); } }
Why This Works
By providing the full parameterized type Map<String, Object>, GraalVM now knows to recursively apply type mapping to each value in the map. When it encounters the JS array, it follows the rule you cited: array elements with size ≤ Integer.MAX_VALUE are converted to a List implementation, not a PolyglotMap.
Alternative: Explicitly Convert the Array Value
If you prefer not to use generic types, you can manually convert the array value after getting the map:
@Test public void testList() { try (Context context = Context.create()) { Value value = context.eval("js", JS_CODE); Value result = value.execute(); Map<String, Object> resultMap = result.as(Map.class); // Manually convert the PolyglotMap to a List Value listValue = result.get("listProperty"); List<Object> list = listValue.as(List.class); assertThat(list).containsExactly("listValue"); } }
But this defeats the purpose of recursive automatic conversion, so the parameterized type approach is better for consistent, full-object conversion.
内容的提问来源于stack exchange,提问作者Brecht Yperman




