在 Java 中使用 Jackson 反序列化异常/抛出的问题

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17855538/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-01 17:49:01  来源:igfitidea点击:

Issues while deserializing exception/throwable using Hymanson in Java

javaHymansondeserialization

提问by Hymanall

I am facing issues while deserializing Exceptionand Throwableinstances using Hymanson (version 2.2.1). Consider the following snippet:

我在使用 Hymanson(版本 2.2.1)反序列化ExceptionThrowable实例时遇到问题。考虑以下片段:

public static void main(String[] args) throws IOException
{
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
    objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
    objectMapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);

    try {
        Integer.parseInt("String");
    }
    catch (NumberFormatException e) {
        RuntimeException runtimeException = new RuntimeException(e);
        String serializedException = objectMapper.writeValueAsString(runtimeException);
        System.out.println(serializedException);
        Throwable throwable = objectMapper.readValue(serializedException, Throwable.class);
        throwable.printStackTrace();
    }
}

The output of System.out.printlnin the catchblock is:

的输出System.out.println中的catch块是:

{
  "@class" : "java.lang.RuntimeException",
  "detailMessage" : "java.lang.NumberFormatException: For input string: \"String\"",
  "cause" : {
    "@class" : "java.lang.NumberFormatException",
    "detailMessage" : "For input string: \"String\"",
    "cause" : null,
    "stackTrace" : [ {
      "declaringClass" : "java.lang.NumberFormatException",
      "methodName" : "forInputString",
      "fileName" : "NumberFormatException.java",
      "lineNumber" : 65
    }, {
      "declaringClass" : "java.lang.Integer",
      "methodName" : "parseInt",
      "fileName" : "Integer.java",
      "lineNumber" : 492
    }, {
      "declaringClass" : "java.lang.Integer",
      "methodName" : "parseInt",
      "fileName" : "Integer.java",
      "lineNumber" : 527
    }, {
      "declaringClass" : "test.Hymanson.HymansonTest",
      "methodName" : "main",
      "fileName" : "HymansonTest.java",
      "lineNumber" : 26
    } ],
    "suppressedExceptions" : [ "java.util.ArrayList", [ ] ]
  },
  "stackTrace" : [ {
    "declaringClass" : "test.Hymanson.HymansonTest",
    "methodName" : "main",
    "fileName" : "HymansonTest.java",
    "lineNumber" : 29
  } ],
  "suppressedExceptions" : [ "java.util.ArrayList", [ ] ]
}

which seems fine. But when I attempt to deserialize this using objectMapper.readValue(), I get the following exception:

这似乎很好。但是当我尝试使用 反序列化它时objectMapper.readValue(),我得到以下异常:

Exception in thread "main" com.fasterxml.Hymanson.databind.exc.UnrecognizedPropertyException: Unrecognized field "declaringClass" (class java.lang.StackTraceElement), not marked as ignorable
 at [Source: java.io.StringReader@3c5ebd39; line: 9, column: 27] (through reference chain: java.lang.StackTraceElement["declaringClass"])
    at com.fasterxml.Hymanson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:79)
    at com.fasterxml.Hymanson.databind.DeserializationContext.reportUnknownProperty(DeserializationContext.java:555)
    at com.fasterxml.Hymanson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:708)
    at com.fasterxml.Hymanson.databind.deser.std.JdkDeserializers$StackTraceElementDeserializer.deserialize(JdkDeserializers.java:414)
    at com.fasterxml.Hymanson.databind.deser.std.JdkDeserializers$StackTraceElementDeserializer.deserialize(JdkDeserializers.java:380)
    at com.fasterxml.Hymanson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:151)
...

I then tried using mix-in annotations, to ignore declaringClassin java.lang.StackTraceElement, but now the deserialized Exceptiondoesn't contain the declaring class in its stack trace:

然后我尝试使用混合注释来忽略declaringClassin java.lang.StackTraceElement,但现在反序列化Exception的堆栈跟踪中不包含声明类:

java.lang.RuntimeException: java.lang.NumberFormatException: For input string: "String"
    at .main(HymansonTest.java:33)
Caused by: java.lang.NumberFormatException: For input string: "String"
    at .forInputString(NumberFormatException.java:65)
    at .parseInt(Integer.java:492)
    at .parseInt(Integer.java:527)
    at .main(HymansonTest.java:30)

Am I missing anything? Any help is greatly appreciated.

我错过了什么吗?任何帮助是极大的赞赏。

采纳答案by Hymanall

There seems to be a Hymanson JIRA entry for this here. Hymanson doesn't seem to be able to handle the declaringClassin java.lang.StackTraceElement, since the getter corresponding to this field is called getClassName().

似乎有此Hyman逊JIRA进入这里。Hymanson 似乎无法处理declaringClassin java.lang.StackTraceElement,因为与该字段对应的 getter 被称为getClassName()

I fixed this issue by using a custom wrapper around StackTraceElementas suggested in the JIRA entry mentioned above. The custom wrapper (CustomStackTraceElement) will have the fields declaringClass, methodName, fileName, and lineNumberand the corresponding getters and setters in it. I modified the catchblock (mentioned in the question) to be as follows:

StackTraceElement按照上面提到的 JIRA 条目中的建议使用自定义包装器解决了这个问题。定制包装(CustomStackTraceElement)将有场declaringClassmethodNamefileName,和lineNumber以及相应的在它的getter和setter。我修改了catch块(在问题中提到)如下:

catch (NumberFormatException e) {
    RuntimeException runtimeException = new RuntimeException(e);
    e.printStackTrace();
    String serializedException = objectMapper.writeValueAsString(runtimeException);
    System.out.println(serializedException);

    String serializedStackTrace = objectMapper.writeValueAsString(transformStackTrace(runtimeException));
    String serializedStackTraceForCause = objectMapper.writeValueAsString(transformStackTrace(runtimeException.getCause()));

    Throwable throwable = objectMapper.readValue(serializedException, Throwable.class);
    List<CustomStackTraceElement> customStackTraceElementList = objectMapper.readValue(serializedStackTrace, List.class);
    List<CustomStackTraceElement> customStackTraceElementListForCause = objectMapper.readValue(serializedStackTraceForCause, List.class);

    throwable.setStackTrace(reverseTransformStackTrace(customStackTraceElementList));
    throwable.getCause().setStackTrace(reverseTransformStackTrace(customStackTraceElementListForCause));
    throwable.printStackTrace();
}

The StackTraceElement[]will be converted into List<CustomStackTraceElement>by the following method during serialization:

所述StackTraceElement[]将被转换成List<CustomStackTraceElement>通过以下方法在序列化过程:

private static List<CustomStackTraceElement> transformStackTrace(Throwable throwable)
{
    List<CustomStackTraceElement> list = new ArrayList<>();
    for (StackTraceElement stackTraceElement : throwable.getStackTrace()) {
        CustomStackTraceElement customStackTraceElement =
            new CustomStackTraceElement(stackTraceElement.getClassName(),
                                        stackTraceElement.getMethodName(),
                                        stackTraceElement.getFileName(),
                                        stackTraceElement.getLineNumber());

        list.add(customStackTraceElement);
    }

    return list;
}

... and the reverse transformation will be done during deserialization:

...并且在反序列化期间将进行反向转换:

private static StackTraceElement[] reverseTransformStackTrace(List<CustomStackTraceElement> customStackTraceElementList)
{
    StackTraceElement[] stackTraceElementArray = new StackTraceElement[customStackTraceElementList.size()];
    for (int i = 0; i < customStackTraceElementList.size(); i++) {
        CustomStackTraceElement customStackTraceElement = customStackTraceElementList.get(i);
        StackTraceElement stackTraceElement =
            new StackTraceElement(customStackTraceElement.getDeclaringClass(),
                                  customStackTraceElement.getMethodName(),
                                  customStackTraceElement.getFileName(),
                                  customStackTraceElement.getLineNumber());

        stackTraceElementArray[i] = stackTraceElement;
    }

    return stackTraceElementArray;
}

Now, after deserialization, the Throwableobject has the expected stack trace in it.

现在,反序列化后,该Throwable对象中包含预期的堆栈跟踪。

回答by Michael Cheremuhin

Add this:

添加这个:

objectMapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

And make out of the deserialized exception the same way, as for the first time:

并以与第一次相同的方式解决反序列化异常:

System.out.println( objectMapper.writeValueAsString( throwable ) );


I used the following code:

我使用了以下代码:

public static void main( String[] args ) throws IOException
{
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure( SerializationFeature.INDENT_OUTPUT, true );
    objectMapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    objectMapper.setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY );
    objectMapper.enableDefaultTyping( ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY );

    try
    {
        Integer.parseInt( "String" );
    }
    catch( NumberFormatException e )
    {
        Throwable throwable = objectMapper.readValue( objectMapper.writeValueAsString( e ), Throwable.class );
        System.out.println( objectMapper.writeValueAsString( throwable ) );
    }
}

Added this jars: Hymanson-annotations-2.2.0.jar, Hymanson-core-2.2.0.jar and Hymanson-databind-2.2.0.jar.

添加了这个 jars:Hymanson-annotations-2.2.0.jar、Hymanson-core-2.2.0.jar 和 Hymanson-databind-2.2.0.jar。

After execution, the following is printed:

执行后打印如下:

{
"@class" : "java.lang.NumberFormatException",
"detailMessage" : "For input string: \"String\"",
"cause" : null,
"stackTrace" : [ {
    "declaringClass" : "java.lang.NumberFormatException",
    "methodName" : "forInputString",
    "fileName" : "NumberFormatException.java",
    "lineNumber" : 48,
    "className" : "java.lang.NumberFormatException",
    "nativeMethod" : false
}, {
    "declaringClass" : "java.lang.Integer",
    "methodName" : "parseInt",
    "fileName" : "Integer.java",
    "lineNumber" : 449,
    "className" : "java.lang.Integer",
    "nativeMethod" : false
}, {
    "declaringClass" : "java.lang.Integer",
    "methodName" : "parseInt",
    "fileName" : "Integer.java",
    "lineNumber" : 499,
    "className" : "java.lang.Integer",
    "nativeMethod" : false
}, {
    "declaringClass" : "com.sample.bla.Main",
    "methodName" : "main",
    "fileName" : "Main.java",
    "lineNumber" : 24,
    "className" : "com.sample.bla.Main",
    "nativeMethod" : false
}, {
    "declaringClass" : "sun.reflect.NativeMethodAccessorImpl",
    "methodName" : "invoke0",
    "fileName" : "NativeMethodAccessorImpl.java",
    "lineNumber" : -2,
    "className" : "sun.reflect.NativeMethodAccessorImpl",
    "nativeMethod" : true
}, {
    "declaringClass" : "sun.reflect.NativeMethodAccessorImpl",
    "methodName" : "invoke",
    "fileName" : "NativeMethodAccessorImpl.java",
    "lineNumber" : 39,
    "className" : "sun.reflect.NativeMethodAccessorImpl",
    "nativeMethod" : false
}, {
    "declaringClass" : "sun.reflect.DelegatingMethodAccessorImpl",
    "methodName" : "invoke",
    "fileName" : "DelegatingMethodAccessorImpl.java",
    "lineNumber" : 25,
    "className" : "sun.reflect.DelegatingMethodAccessorImpl",
    "nativeMethod" : false
}, {
    "declaringClass" : "java.lang.reflect.Method",
    "methodName" : "invoke",
    "fileName" : "Method.java",
    "lineNumber" : 597,
    "className" : "java.lang.reflect.Method",
    "nativeMethod" : false
}, {
    "declaringClass" : "com.intellij.rt.execution.application.AppMain",
    "methodName" : "main",
    "fileName" : "AppMain.java",
    "lineNumber" : 120,
    "className" : "com.intellij.rt.execution.application.AppMain",
    "nativeMethod" : false
    } ],
    "message" : "For input string: \"String\"",
    "localizedMessage" : "For input string: \"String\""
}

回答by Andrei I

It seems that the output you get in version 2.2.1 is not the same as I get with version 2.2.0 (which according to the website is the latest 2.x version). Besides the latest available 2.x version on the Maven Repository is 2.2.2. So I would try to either downgrade it to 2.2.0 or to upgrade it to 2.2.2. If any of the changes brings you the expected result, I would go further with that version and open a BUG in Hymanson's JIRA.

您在 2.2.1 版中获得的输出似乎与我在 2.2.0 版中获得的输出不同(根据网站,这是最新的 2.x 版)。除了 Maven 存储库上最新的可用 2.x 版本是 2.2.2。所以我会尝试将其降级到 2.2.0 或升级到 2.2.2。如果任何更改为您带来了预期的结果,我会进一步使用该版本并在 Hymanson 的 JIRA 中打开一个 BUG。

And of course don't forget

当然不要忘记

objectMapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

from Michael's answer.

来自迈克尔的回答。

回答by Markus Duft

I've had a similar issue. I'm using this code now, and it allows me to serialize and deserialize exceptions with proper types (i.e. a RuntimeExceptionwill be a RuntimeExceptionagain :)):

我有过类似的问题。我现在正在使用此代码,它允许我使用正确的类型序列化和反序列化异常(即 aRuntimeExceptionRuntimeException再次成为 a :)):

public static ObjectMapper createObjectMapper() {
    ObjectMapper mapper = new ObjectMapper(null, null, new DefaultDeserializationContext.Impl(
            new BeanDeserializerFactory(new DeserializerFactoryConfig()) {
                private static final long serialVersionUID = 1L;

                @Override
                public JsonDeserializer<Object> buildThrowableDeserializer(
                        DeserializationContext ctxt, JavaType type, BeanDescription beanDesc)
                        throws JsonMappingException {
                    return super.buildBeanDeserializer(ctxt, type, beanDesc);
                }

            }));

    mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
    mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
    mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

    mapper.addMixIn(Throwable.class, ThrowableMixin.class);
    mapper.addMixIn(StackTraceElement.class, StackTraceElementMixin.class);

    return mapper;
}

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
@JsonAutoDetect(fieldVisibility = Visibility.ANY)
@JsonIgnoreProperties({ "message", "localizedMessage", "suppressed" })
abstract class ThrowableMixin {

    @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "$id")
    private Throwable cause;
}

abstract class StackTraceElementMixin {

    @JsonProperty("className")
    private String declaringClass;

}

I'm manipulating the BeanDeserializerFactoryto make buildThrowableDeserializernot treat Throwableany special but just like any other Object. Then using Mixinsto define the "special" handling of Throwableand StackTraceElementto my liking.

我正在操纵BeanDeserializerFactory使buildThrowableDeserializer不处理Throwable任何特殊但就像任何其他Object. 然后,使用Mixins来定义“特殊”处理的ThrowableStackTraceElement我的胃口。

回答by Chas

Try using polymorphism so that Hymanson deserializer knows what kind of Throwable to create:

尝试使用多态,以便 Hymanson 反序列化器知道要创建什么样的 Throwable:

/**
 * Hymanson module to serialize / deserialize Throwable
 */
public class ThrowableModule extends SimpleModule {
  public ThrowableModule() {
    super("Throwable", new Version(1, 0, 0, null, null, null));
  }

  @Override
  public void setupModule(SetupContext context) {
    context.setMixInAnnotations(Throwable.class, ThrowableAnnotations.class);
  }

  /**
   * Add annotation to Throwable so that the class name is serialized with the instance data.
   */
  @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "class")
  static abstract class ThrowableAnnotations {
  }
}

回答by Michael Cheremuhin

Is it so necessary to use json serialization? Looks liks there are some bugs with throwables. Why not use system api:

有那么必要用json序列化吗?看起来像 throwables 有一些错误。为什么不使用系统api:

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(  );
ObjectOutputStream objectOutputStream = new ObjectOutputStream( byteArrayOutputStream );
objectOutputStream.writeObject( e );

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( byteArrayOutputStream.toByteArray() );
ObjectInputStream objectInputStream = new ObjectInputStream( byteArrayInputStream );
Throwable t = (Throwable) objectInputStream.readObject();