如何使用Jackson将简单和复杂的JSON类型解析为Java对象列表

ogsagwnx  于 2023-01-04  发布在  Java
关注(0)|答案(2)|浏览(161)

我正在开发一个Java库,以便能够读/写Lottie数据(定义为JSON的动画)。我试图用最少的代码和记录来实现这一点,但要为Lottie格式中定义的所有可能的用例实现这一点有点挑战性。我已经能够处理几乎所有的事情,但我仍然需要为关键帧找到一个合适的解决方案。
在下面的单元测试中,应该如何定义Java对象才能解析示例JSON?这在“pureJackson”中可能吗?还是需要一个helper类?我使用的是Jackson 2.14.1。
此时,只有testTimed成功。

public class KeyframeTest {

    private static final ObjectMapper mapper = new ObjectMapper();

    @Test
    void testInteger() throws JsonProcessingException {
        var json = """
                {
                    "k": [
                      128,
                      256
                    ]
                }
                """;

        var animated = mapper.readValue(json, Animated.class);

        assertAll(
                () -> assertEquals(2, animated.keyframes().size()),
                () -> assertTrue(animated.keyframes().get(0) instanceof NumberKeyframe),
                () -> assertEquals(128, animated.keyframes().get(0)),
                () -> JSONAssert.assertEquals(json, mapper.writeValueAsString(animated), false)
        );
    }

    @Test
    void testDouble() throws JsonProcessingException {
        var json = """
                {
                    "k": [
                      5.01,
                      6.02
                    ]
                }
                """;

        var animated = mapper.readValue(json, Animated.class);

        assertAll(
                () -> assertEquals(2, animated.keyframes().size()),
                () -> assertTrue(animated.keyframes().get(0) instanceof NumberKeyframe),
                () -> assertEquals(5.01, animated.keyframes().get(0)),
                () -> JSONAssert.assertEquals(json, mapper.writeValueAsString(animated), false)
        );
    }

    @Test
    void testTimed() throws JsonProcessingException {
        var json = """
                {
                    "k": [
                     {
                       "i": {
                         "x": [
                           0.833
                         ],
                         "y": [
                           0.833
                         ]
                       },
                       "o": {
                         "x": [
                           0.167
                         ],
                         "y": [
                           0.167
                         ]
                       },
                       "t": 60,
                       "s": [
                         1.1,
                         2.2,
                         3.3
                       ]
                     },
                     {
                       "t": 60,
                       "s": [
                         360.0
                       ]
                     }
                   ]
                }
                """;

        var animated = mapper.readValue(json, Animated.class);

        assertAll(
                () -> assertEquals(2, animated.keyframes().size()),
                () -> assertTrue(animated.keyframes().get(0) instanceof TimedKeyframe),
                () -> assertEquals(60, ((TimedKeyframe) animated.keyframes().get(0)).time()),
                () -> JSONAssert.assertEquals(json, mapper.writeValueAsString(animated), false)
        );
    }

    @Test
    void testMixed() throws JsonProcessingException {
        var json = """
                {
                    "k": [
                    100,
                    33.44,
                     {
                       "t": 60,
                       "s": [
                         1.1,
                         2.2,
                         3.3
                       ]
                     }
                   ]
                }
                """;

        var keyFrames = mapper.readValue(json, new TypeReference<List<Keyframe>>() {
        });

        assertAll(
                () -> assertEquals(3, keyFrames.size()),
                () -> assertTrue(keyFrames.get(0) instanceof NumberKeyframe),
                () -> assertTrue(keyFrames.get(1) instanceof NumberKeyframe),
                () -> assertTrue(keyFrames.get(2) instanceof TimedKeyframe),
                () -> JSONAssert.assertEquals(json, mapper.writeValueAsString(keyFrames), false)
        );
    }
}

动画对象

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_NULL)
public record Animated(
        @JsonProperty("a") Integer animated,
        @JsonProperty("k") List<Keyframe> keyframes,
        @JsonProperty("ix") Integer ix,
        @JsonProperty("l") Integer l
) {
}

关键帧对象,基于我前面的问题使用Java记录使用fasterxml将JSON解析为Java记录。Jackson

@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
@JsonSubTypes({
        @JsonSubTypes.Type(TimedKeyframe.class),
        @JsonSubTypes.Type(NumberKeyframe.class)
})
public interface Keyframe {

}

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record NumberKeyframe(
        BigDecimal value
) implements Keyframe {
}

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record TimedKeyframe(
        @JsonProperty("t") Integer time, // in frames
        // Use BigDecimal here to be able to handle both Integer and Double
        // https://stackoverflow.com/questions/40885065/jackson-mapper-integer-from-json-parsed-as-double-with-drong-precision
        @JsonProperty("s") List<BigDecimal> values,
        @JsonProperty("i") EasingHandle easingIn,
        @JsonProperty("o") EasingHandle easingOut,
        @JsonProperty("h") Integer holdFrame
) implements Keyframe {
}

这是testDouble的失败消息:

com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class com.lottie4j.core.model.keyframe.Keyframe]: Unexpected input
 at [Source: (String)"{
    "k": [
      5.01,
      6.02
    ]
}
"; line: 3, column: 7] (through reference chain: com.lottie4j.core.model.Animated["k"]->java.util.ArrayList[0])
6tdlim6h

6tdlim6h1#

看起来Jackson在将数字反序列化为对象时遇到了问题。您可以使用自定义反序列化器或通过将NumberKeyFrame扩展为BigDecimal来解决此问题。下面是一个工作的最小示例,但我删除了您的许多代码。请注意JsonTypeInfo注解中的defaultImpl。为了正常工作,这是必要的,尽管我不是100%为什么:see_no_evil:

public class MyTest {

    @JsonIgnoreProperties(ignoreUnknown = true)
    static class Animated {

        public @JsonProperty("k") List<Keyframe> keyframes = new ArrayList<>();
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION, defaultImpl = NumberKeyframe.class)
    @JsonSubTypes({
        @JsonSubTypes.Type(NumberKeyframe.class),
        @JsonSubTypes.Type(TimedKeyframe.class),
    })
    public interface Keyframe {

    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    static class NumberKeyframe extends BigDecimal implements Keyframe {

        public NumberKeyframe(int val) {
            super(val);
        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    static class TimedKeyframe implements Keyframe {

        @JsonProperty("s")
        List<BigDecimal> values;
    }

    private static final ObjectMapper mapper = new ObjectMapper();

    @Test
    public void testInteger() throws JsonProcessingException, JSONException {
        var json = "{\"k\": [128,256]}";

        var animated = mapper.readValue(json, Animated.class);
        assertEquals(2, animated.keyframes.size());

        assertTrue(animated.keyframes.get(0) instanceof NumberKeyframe);
        assertEquals(128, ((NumberKeyframe) animated.keyframes.get(0)).intValue());
    }

    @Test
    void testTimed() throws JsonProcessingException {
        var json = "{\"k\": [\n"
            + "  {\"i\": {\"x\": [0.833],\"y\": [0.833]},\"o\": {\"x\": [0.167],\"y\": [0.167]},\"t\": 60,\"s\": [1.1,2.2,3.3]},\n"
            + "  { \"t\": 60, \"s\": [360.0]}\n"
            + "]}";

        var animated = mapper.readValue(json, Animated.class);

        assertEquals(2, animated.keyframes.size());
        assertTrue(animated.keyframes.get(0) instanceof TimedKeyframe);
        assertEquals(1.1, ((TimedKeyframe) animated.keyframes.get(0)).values.get(0).doubleValue());
        assertEquals(2.2, ((TimedKeyframe) animated.keyframes.get(0)).values.get(1).doubleValue());
        assertEquals(3.3, ((TimedKeyframe) animated.keyframes.get(0)).values.get(2).doubleValue());
    }
}
rsaldnfx

rsaldnfx2#

Map似乎不正确。您得到的是:

var json = """
           {
               "k": [
                   5.01,
                   6.02
                ]
           }
           """;

这实际上是一个数字数组,我假设它应该Map到NumberKeyframe,但它会Map到如下内容:

"k": [{"value": 5.01}, {"value": 6.01}]

我不确定Jackson是否能在没有Map器的情况下无缝地完成这件事。

相关问题