Сериализация / десериализация исключений без трассировки стека с использованием Jackson - PullRequest
2 голосов
/ 23 октября 2019

Я пытаюсь создать класс с java.lang.Exception, хранящимся в качестве поля. Также я пытаюсь исключить трассировку стека из сериализации / десериализации с использованием аннотации @JsonIgnoreProperties.

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

class ExWrapper {
    @JsonIgnoreProperties({"stackTrace"})
    public Exception ex;

    @Override
    public String toString() {
        return "ExWrapper{" +
                "ex=" + ex +
                '}';
    }
}

public class Example {
    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        ExWrapper exw = new ExWrapper();
        exw.ex = new Exception("Oops");
        String str = mapper.writeValueAsString(exw);
        System.out.println(str);
        ExWrapper exW = mapper.readValue(str, ExWrapper.class);
        System.out.println(exW);
    }
}

Ошибка результата довольно удивительна, Джексон не может найти поле message:

Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "message" (class java.lang.Exception), not marked as ignorable (one known property: "cause"])
 at [Source: (String)"{"ex":{"cause":null,"message":"Oops","suppressed":[],"localizedMessage":"Oops"}}"; line: 1, column: 32] (through reference chain: ExWrapper["ex"]->java.lang.Exception["message"])
    at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)
    at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:840)
    at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1179)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1592)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1570)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:375)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
    at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4202)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3205)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3173)
    at Example.main(Example.java:25)

Ну, в классе Throwable есть только getMessage, а setMessage нет, так что это кажется немного разумным, если вы не попытаетесь удалить аннотацию @JsonIgnoreProperties. Он работает как чудо: он сериализует и десериализует обратно должным образом, и отсутствие установщика для message внезапно не является проблемой. Добавление "message" к пропущенным полям также заставляет его работать (но без сообщения об исключении).

Я попытался произвольно войти в код Джексона с помощью отладчика и обнаружил, что при отсутствии @JsonIgnoreProperties в конечном итоге ThrowableDeserializerМетоды вызываются и не вызываются, когда присутствует аннотация. ThrowableDeserializer, похоже, имеет некоторые хаки, специфичные для сообщения об исключенииЯ предполагаю, что ThrowableDeserializer нежизнеспособен, когда отсутствует трассировка стека, и Джексон возвращается к сериализатору Java-бина по умолчанию.

Вопрос в том, что именно здесь происходит и как его решить.

1 Ответ

1 голос
/ 24 октября 2019

Версия 2

ThrowableDeserializer класс расширяет BeanDeserializer, поэтому эти двое имеют общий код для создания и десериализации POJO. Exception не является обычным POJO и должно обрабатываться иначе. Поскольку он не предоставляет многим установщикам, нам нужно использовать конструктор, чтобы создать его с сообщением и другими полями, которые мы можем пропустить. Чтобы зарегистрировать конструктор, мы можем использовать функцию MixIn:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.File;

public class JsonPathApp {

    public static void main(String[] args) throws Exception {
        File jsonFile = new File("./resource/test.json").getAbsoluteFile();

        ObjectMapper mapper = new ObjectMapper();
        mapper.addMixIn(Exception.class, ExceptionMixIn.class);
        mapper.addMixIn(Throwable.class, ThrowableMixIn.class);

        ExWrapper exW = mapper.readValue(jsonFile, ExWrapper.class);
        exW.ex.printStackTrace();
    }
}

@JsonIgnoreProperties("stackTrace")
abstract class ExceptionMixIn extends Exception {

    @JsonCreator
    public ExceptionMixIn(@JsonProperty("message") String message) {
        super(message);
    }
}

@JsonIgnoreProperties("stackTrace")
abstract class ThrowableMixIn extends Throwable {

    @JsonCreator
    public ThrowableMixIn(@JsonProperty("message") String message) {
        super(message);
    }
}

class ExWrapper {

    public Exception ex;

    @Override
    public String toString() {
        return "ExWrapper{" +
                "ex=" + ex +
                '}';
    }
}

Выше кода для JSON с исключением с указанием причины:

java.lang.Exception: Opps
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
    at com.fasterxml.jackson.databind.introspect.AnnotatedConstructor.call(AnnotatedConstructor.java:124)
    at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromObjectWith(StdValueInstantiator.java:283)
    at com.fasterxml.jackson.databind.deser.ValueInstantiator.createFromObjectWith(ValueInstantiator.java:229)
    at com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator.build(PropertyBasedCreator.java:195)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:422)
    at com.fasterxml.jackson.databind.deser.std.ThrowableDeserializer.deserializeFromObject(ThrowableDeserializer.java:65)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
    at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2902)
Caused by: java.lang.Throwable: Root oops
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
    at com.fasterxml.jackson.databind.introspect.AnnotatedConstructor.call(AnnotatedConstructor.java:124)
    at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromObjectWith(StdValueInstantiator.java:283)
    at com.fasterxml.jackson.databind.deser.ValueInstantiator.createFromObjectWith(ValueInstantiator.java:229)
    at com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator.build(PropertyBasedCreator.java:195)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:422)
    at com.fasterxml.jackson.databind.deser.std.ThrowableDeserializer.deserializeFromObject(ThrowableDeserializer.java:65)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:530)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:528)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:449)
    ... 8 more

Как видите, исключение имеетпо умолчанию stackTrace и stackTrace из JSON полезные данные пропущены.

Версия 1

Я не слишком много копал, но включение allowSetters решает эту проблему:

@JsonIgnoreProperties(value = {"stackTrace"}, allowSetters = true)
public Exception ex;

Ваш код с этим изменением печатается:

{"ex":{"cause":null,"message":"Oops","localizedMessage":"Oops","suppressed":[]}}
ExWrapper{ex=java.lang.Exception: Oops}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...