首页 > 解决方案 > GraalSDK 值作为 JSON 结构

问题描述

我试图让 JavaScript 调用的结果类似于 Map<String,Object> 的 JSON 结构,其中值可能是数字、字符串、布尔值、对象(地图)或数组(列表),类似于杰克逊所做的如果将值转换为地图。

使用Value.as(Map.class) 调用时,我希望 Map 中的值遵循这些规则

如果使用原始 Map.class 或 Object 组件类型,则列表的返回类型递归地服从 Object 目标类型映射规则。

进一步了解文档(对象映射规则 8)

如果该值具有数组元素并且其数组大小小于或等于 Integer.MAX_VALUE,则结果值将实现 List。

然而这个测试失败了

public class TestGraalMap {
    static String JS_CODE = "(function myFun(){ return { listProperty: ['listValue']};})";

    @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);
            assertThat(resultMap).hasEntrySatisfying("listProperty", testArray -> {
                assertThat(testArray).asList().containsExactly("listValue");
            });
        }
    }
}

出现以下错误。

 Expecting:
  <{}>
to be an instance of:
  <java.util.List>
but was instance of:
  <com.oracle.truffle.polyglot.PolyglotMap>

我错过了什么?

标签: javagraalvmgraaljs

解决方案


是的,有关于默认对象目标强制的规则 8 [1]。然而,这些规则是按顺序执行的。因此,在此之前,规则 7 规定如果 Value 有成员,那么它将被转换为 Map。此规则适用于 JavaScript 对象,因为它们同时具有成员和数组元素。可以说这种行为是违反直觉的,但 GraalVM 从 Nashorn 等其他引擎继承了这一点。GraalVM 团队仍在讨论默认情况下是否可以更改此行为,但那是 tbd。

同时,polyglot API 有一个称为目标类型映射的简洁功能,您可以使用它以您需要的方式自定义这些映射。这是您的情况所需要的:

static String JS_CODE = "(function myFun(){ return { listProperty: ['listValue']};})";

@Test
public static void test {
    HostAccess access = HostAccess.newBuilder(HostAccess.EXPLICIT)
            .targetTypeMapping(
                    // for any conversion Any Value -> Object
                    Value.class, Object.class, 
                    // if the value has array elements and members
                    (v) -> v.hasArrayElements() && v.hasMembers(), 
                    // convert to List (instead of Map)
                    (v) -> v.as(List.class)).build();
    
    try (Context context = Context.newBuilder().allowHostAccess(access).build()) {
        Value value = context.eval("js", JS_CODE);
        Value result = value.execute();
        Map<String, Object> resultMap = result.as(Map.class);
        assertThat(resultMap).hasEntrySatisfying("listProperty", testArray -> {
            assertThat(testArray).asList().containsExactly("listValue");
        });
    }
}

[1] https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html#as-java.lang.Class-


推荐阅读