ObjectMapper readValue вызывается несколько раз, пока не будет создано исключение StackOverflowException

Я пытаюсь создать собственный класс десериализатора для своего класса значений. Но ObjectMapper .readValue вызывается несколько раз, пока не будет выбрано исключение StackOverflowException. Ниже приведен только пример:

@JsonDeserialize(using = UserDeserializer.class)
@Getter
public class User {
    private String name;
}

public class UserDeserializer extends StdDeserializer<User> {
    public UserDeserializer() {
        super(User.class);
    }

    @Override
    public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        return ctxt.readValue(p, User.class);
    }
}

public class UserDeserializerTest {
    @Test
    public void whenUserIsDeserializedFromJsonThenNameShouldEqualBob() {
        String = "{ \"name\" : \"bob\" }";
        ObjectMapper mapper = new ObjectMapper();
        User user = mapper.readValue(json, User.class);
        assertThat(user.getName).isEqualTo("bob");
    }
}

Зависимости Maven:

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.6.1</version>
    </dependency>
    <dependency>
        <groupId>org.assertj</groupId>
        <artifactId>assertj-core</artifactId>
        <version>1.7.1</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.2</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Выдается следующее исключение StackOverflowException:

Exception in thread "main" java.lang.StackOverflowError
...

// The following lines are repeated.

    at com.fasterxml.jackson.databind.DeserializationContext.readValue(DeserializationContext.java:754)
    at com.fasterxml.jackson.databind.DeserializationContext.readValue(DeserializationContext.java:746)
    at example.UserDeserializer.deserialize(UserDeserializer.java:25)
    at example.UserDeserializer.deserialize(UserDeserializer.java:15)

Process finished with exit code 1

Я попытался зарегистрировать десериализатор через модуль и удалить @JsonAnnotation, но по-прежнему выдает то же исключение:

SimpleModule module = new SimpleModule("User Simple Module");
module.addDeserializer(User.class, new UserDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.addModule(module);
mapper.readValue(json, User.class);

Затем я попытался создать новый ObjectMapper в моем UserDeserializer и удалить @JsonDeserialize из моего класса User, и мои тесты прошли.

@Override
public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    return new ObjectMapper().readValue(p, User.class);
}

Выполняя вышеуказанные работы, я хочу зарегистрировать десериализатор для каждого класса, используя аннотацию @JsonDeserialize, а не регистрировать глобальный пользовательский сопоставитель объектов. У меня есть причины, по которым я этого хочу.

Я пробовал другие комбинации кода, например:

// Add the 'as' keyword to the annotation
@JsonDeserialise(using = UserDeserialize.class, as = User.class)

// Locally assigning a mapper from the JsonParser inside the UserDeserializer class
ObjectMapper mapper = (ObjectMapper) p.getCodec();
mapper.readValue(p, User.class);

// Constructing a new JSON Parser inside the deserializer
ObjectMapper mapper = (ObjectMapper) p.getCodec();
JsonFactory factory = mapper.getFactory();
JsonParser parser = factory.createParser(p.getText());
return (User) parser.readValueAs(handledType());

// Reading the Json into a Tree
ObjectMapper mapper = (ObjectMapper) p.getCodec();
ObjectNode root = (ObjectNode) p.readValueAsTree();
return mapper.readValue(root.traverse(), User.class);

Но чтение JSON в дерево бросает:

Exception in thread "main" java.lang.RuntimeException: java.lang.IllegalStateException: No ObjectCodec defined for parser, needed for deserialization
    at example.User.main(User.java:46)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
Caused by: java.lang.IllegalStateException: No ObjectCodec defined for parser, needed for deserialization
    at com.fasterxml.jackson.core.JsonParser._codec(JsonParser.java:1565)
    at com.fasterxml.jackson.core.JsonParser.readValueAsTree(JsonParser.java:1559)
    at example.UserDeserializer.deserialize(UserDeserializer.java:37)
    at example.UserDeserializer.deserialize(UserDeserializer.java:17)
    at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:3674)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1996)
    at example.UserDeserializer.deserialize(UserDeserializer.java:38)
    at example.UserDeserializer.deserialize(UserDeserializer.java:17)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3702)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2714)
    at example.User.main(User.java:36)
    ... 5 more

Я также попытался реализовать ContextualDeserializer внутри моего UserDeserializer, но все равно получил то же исключение StackOverflowException, что и раньше:

private UserDeserializer(Class<?> vc) {
    super(vc);
}

public JsonDeserializer<User> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
    return new UserDeserializer(ctxt.getContextualType().getRawClass());
}

@Override
public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    return (User) new ObjectMapper().readValue(p, handledType());
}

Я надеюсь, что это просто плохой случай ошибки PEBKAC, и решение легко доступно с вашей помощью / руководством.

С уважением


person Gary    schedule 03.09.2015    source источник
comment
Просто имейте в виду, что неповторяющаяся вершина трассировки стека не имеет отношения к диагностике.   -  person Marko Topolnik    schedule 03.09.2015
comment
Спасибо, Марко. Я удалил ненужную часть трассировки стека и оставил самую важную часть.   -  person Gary    schedule 03.09.2015
comment
Вы проверили, что происходит на DeserializationContext.java:754? Вы должны иметь возможность отслеживать вызовы и видеть, что заставляет их перезванивать самим себе.   -  person Marko Topolnik    schedule 03.09.2015
comment
@Gary мало что знает о Джексоне, но разве этот оператор ctxt.readValue(p, User.class); в десериализаторе не переходит в бесконечный цикл, снова вызывая тот же метод? Я имею в виду, что вам нужно создать новый пользовательский объект, десериализовать отдельные поля, настроить их и вернуть.   -  person Dakshinamurthy Karra    schedule 03.09.2015
comment
В чем собственно смысл вашего пользовательского десериализатора?   -  person Marko Topolnik    schedule 03.09.2015


Ответы (1)


Я согласен с KDM, попробуйте этот десериализатор:

    public class UserDeserializer extends JsonDeserializer<User> {

    @Override
    public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode node = p.readValueAsTree();
        User user = new User();
        user.setName(node.get("name").asText());
        return user;
    }
}
person Yuriy Ch    schedule 03.09.2015
comment
@Marko - в строке 754 он назначает deser=example.UserDeserializer@771 (_valueClass = class example.User, а затем вызывает метод десериализации десериализатора. Затем ctxt.readValue (p, User.class) затем возвращается к строке 754, очевидно заканчивая порочным кругом. - person Gary; 03.09.2015
comment
@KDM - Вы правы насчет бесконечного цикла. Но я не хочу создавать конкретный класс в десериализаторе. В конечном итоге я пытаюсь использовать общий десериализатор, в котором любой объект может быть реконструирован из JSON. Я надеялся, что магия ObjectMapper поможет мне здесь, вместо того, чтобы пытаться разрешить весь объект через Reflection. У меня есть собственные аннотации, которыми я буду аннотировать свои классы значений, и десериализатор должен обрабатывать их по-разному на этапе построения. - person Gary; 03.09.2015
comment
@KDM - Во время первого цикла создания объекта мои аннотации будут игнорироваться через DeserializationProblemHandler, чтобы сказать, что они будут обработаны позже. После выполнения первого этапа десериализации используйте Reflection, чтобы помочь обнаружить аннотации и создать специализированные классы для добавления к классу значений, который реализует интерфейс контракта. - person Gary; 03.09.2015
comment
@Marko - для создания универсального десериализатора, в котором класс значений реализует общий интерфейс, а десериализатор будет обрабатывать и обрабатывать пользовательские аннотации для класса значений, не зная, как создается класс значений. Аннотации относятся к специализированному классу, который затем создается и через Reflection присваивается классу значений. - person Gary; 03.09.2015