问题
I am trying to create a class with a java.lang.Exception
stored as a field. Also I am trying to exclude stack trace from serialization/deserialization using @JsonIgnoreProperties
annotation.
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);
}
}
The result error is quite surprising, Jackson cannot find the message
field:
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)
Well, there is only getMessage
and no setMessage
in Throwable
class so it seems a bit reasonable unless you try to remove @JsonIgnoreProperties
annotation. It works like a charm: it serializes and deserializes back properly and missing setter for message
is suddenly not a problem. Adding "message"
to ignored fields also make it work (yet without an exception message).
I tried to randomly step in Jackson code using debugger and found out that when @JsonIgnoreProperties
is missing, eventually ThrowableDeserializer
methods are called and they are not called when the annotation is present. ThrowableDeserializer
seems to have some hacks specific to exception message. My guess is that ThrowableDeserializer
is not viable when the stack trace is missing and Jackson falls back to default java bean serializer.
The question is what exactly is going on here and how to solve it.
回答1:
Version 2
ThrowableDeserializer
class extends BeanDeserializer
so these two share some code how to create and deserialise a POJO
. Exception
is not a regular POJO
and should be handled differently. Since it does not provide to many setters we need to use constructor to create it with message and other fields we can skip. To register constructor we can use MixIn
feature:
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 +
'}';
}
}
Above code for a JSON
with exception with a cause prints:
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
As you can see, exception has default stackTrace
and stackTrace
from JSON
payload is skipped.
Version 1
I did not dig it too much but enabling allowSetters
solves this problem:
@JsonIgnoreProperties(value = {"stackTrace"}, allowSetters = true)
public Exception ex;
Your code with this change prints:
{"ex":{"cause":null,"message":"Oops","localizedMessage":"Oops","suppressed":[]}}
ExWrapper{ex=java.lang.Exception: Oops}
来源:https://stackoverflow.com/questions/58530206/serializing-deserializing-exceptions-without-stack-trace-using-jackson