jackson 接口上的`@JsonDeserialize`导致调用堆栈溢出

xdyibdwo  于 2023-10-20  发布在  其他
关注(0)|答案(1)|浏览(180)

我得到了一个接口+ 2个具体的子类,都为Jackson做了注解。这两个子类可以独立工作。这两个子类由字段(K/V对)的 name 区分,而不是某个K/V对的 value

  • 因此,我不能使用任何正常的Jackson PTH功能。或者我可以吗?请让我知道**

我希望他们序列化w/o任何 Package 或注入字段。
我要使用的例子(full MCVE gist is here)是一个BookId接口,其中具体的实现者是ISBNASIN,每个都有一个字段。BookId s的示例数组如下所示:

{ "ids": [
    { "isbn" : "978-0-596-52306-0" },
    { "asin" : "B07QKFQ7QJ" }
]}

所以ISBNASIN非常简单,这里是ISBNASIN类似于不同的验证器方法):

public static class ISBN implements BookId {
    final String isbn;

    @JsonCreator
    public ISBN(@JsonProperty("isbn") String isbn) {
        if (!valid(isbn)) throw new IllegalArgumentException("bad isbn syntax");
        this.isbn = isbn;
    }

    boolean valid(String isbn) { return isbn != null && !isbn.isBlank() /* && checksum ok ... */; }
}

因此,尝试我认为是一种 * 正确 * 的方法-简单地区分这两种情况,然后适当地调用对象Map器-看起来像这样:

@JsonDeserialize(using = BookId.DeserializerDirectViaJackson.class)
public interface BookId {

    class DeserializerDirectViaJackson extends StdDeserializer<BookId> {
        public DeserializerDirectViaJackson() { this(null); }
        public DeserializerDirectViaJackson(final Class<?> vc) { super(vc); }

        @Override
        public BookId deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
            final var om = (ObjectMapper) jsonParser.getCodec();
            final var node = (JsonNode) om.readTree(jsonParser);
            if (!node.isObject()) throw new IllegalStateException("expected JSON object");
            if (node.has("isbn")) return om.treeToValue(node, ISBN.class);
            if (node.has("asin")) return om.treeToValue(node, ASIN.class);
            throw new IllegalStateException("expected 'isbn' or 'asin' field");
        }
    }
...

其中的关键行是:

if (node.has("isbn")) return om.treeToValue(node, ISBN.class);
            if (node.has("asin")) return om.treeToValue(node, ASIN.class);

在这里,我在TreeValue中查找区分键,然后调用ObjectMapper.treeToValue(node, <concreteclass>.class)来正确解析它。
但这会导致无限递归,如图所示(调用堆栈的顶部):

> Task :cli:JacksonInterfaceCustomDeserializerMCVE.main() FAILED
Exception in thread "main" java.lang.StackOverflowError
    at java.base/java.util.LinkedHashMap$LinkedEntryIterator.next(LinkedHashMap.java:788)
    at java.base/java.util.LinkedHashMap$LinkedEntryIterator.next(LinkedHashMap.java:786)
    at com.fasterxml.jackson.databind.node.NodeCursor$ObjectCursor.nextToken(NodeCursor.java:214)
    at com.fasterxml.jackson.databind.node.TreeTraversingParser.nextToken(TreeTraversingParser.java:108)
    at com.fasterxml.jackson.core.JsonParser.nextFieldName(JsonParser.java:1091)
    at com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer._deserializeContainerNoRecursion(JsonNodeDeserializer.java:536)
    at com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer.deserialize(JsonNodeDeserializer.java:100)
    at com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer.deserialize(JsonNodeDeserializer.java:25)
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
    at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:4801)
    at com.fasterxml.jackson.databind.ObjectMapper.readTree(ObjectMapper.java:3084)
    at com.bakinsbits.bookcatalog.JacksonInterfaceCustomDeserializerMCVE$BookId$DeserializerDirectViaJackson.deserialize(JacksonInterfaceCustomDeserializerMCVE.java:58)
    at com.bakinsbits.bookcatalog.JacksonInterfaceCustomDeserializerMCVE$BookId$DeserializerDirectViaJackson.deserialize(JacksonInterfaceCustomDeserializerMCVE.java:51)
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
    at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:4801)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2974)
    at com.fasterxml.jackson.databind.ObjectMapper.treeToValue(ObjectMapper.java:3438)
    at com.bakinsbits.bookcatalog.JacksonInterfaceCustomDeserializerMCVE$BookId$DeserializerDirectViaJackson.deserialize(JacksonInterfaceCustomDeserializerMCVE.java:60)
    at com.bakinsbits.bookcatalog.JacksonInterfaceCustomDeserializerMCVE$BookId$DeserializerDirectViaJackson.deserialize(JacksonInterfaceCustomDeserializerMCVE.java:51)
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
    at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:4801)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2974)

等等等等。

**所以,问题是:**为什么它会这样做,更重要的是-我如何使它工作?

为了比较(并显示我做了工作),下面的解析器 * 确实 * 工作,但有(主要)缺点,我基本上必须自己解析和验证具体的子类。对于这个玩具示例是可行的,但对于真实的用例就不那么可行了。

class DeserializerHomegrown extends StdDeserializer<BookId> {
        public DeserializerHomegrown() { this(null); }
        public DeserializerHomegrown(final Class<?> vc) { super(vc); }

        @Override
        public BookId deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
            final var om = (ObjectMapper) jsonParser.getCodec();
            final var node = (JsonNode) om.readTree(jsonParser);
            if (!node.isObject()) throw new IllegalStateException("expected JSON object");
            if (node.has("isbn")) return new ISBN(getValueNode(node, "isbn"));
            if (node.has("asin")) return new ASIN(getValueNode(node, "asin"));
            throw new IllegalStateException("expected 'isbn' or 'asin' field");
        }

        String getValueNode(JsonNode node, String fieldName) {
            final var field = node.get(fieldName);
            if (field == null) return null;
            if (!field.isValueNode()) throw new IllegalStateException("%s field is not JSON value".formatted(fieldName));
            return field.asText();
        }
    }
wqsoz72f

wqsoz72f1#

您可以使用JsonTypeInfo注解及其演绎系统来演绎与特定属性的存在相关的一种类型的特定子类型:

@JsonTypeInfo(use = Id.DEDUCTION)
@JsonSubTypes({ @Type(ASIN.class), @Type(ISBN.class)})
public interface BookId {}

@Data
public class ISBN implements BookId {
    private String isbn;
}

@Data
public class ASIN implements BookId {
    private String asin;
}

在这种情况下,实现BookId接口的类可以通过它们自己的asin和isbn属性的存在与否来识别,所以你可以像下面这样对BookId数组进行格式化:

输入:

{ "ids": [
    { "isbn" : "978-0-596-52306-0" },
    { "asin" : "B07QKFQ7QJ" }
]}

下面是一个基于json输入的例子:

JsonNode ids = mapper.readTree(json).at("/ids");
BookId[] bookIds = mapper.treeToValue(ids, BookId[].class);
//ok, it prints [ISBN(isbn=978-0-596-52306-0), ASIN(asin=B07QKFQ7QJ)]
System.out.println(Arrays.toString(bookIds));

相关问题