13

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

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();
    }
}

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.jackson.JacksonTest",
      "methodName" : "main",
      "fileName" : "JacksonTest.java",
      "lineNumber" : 26
    } ],
    "suppressedExceptions" : [ "java.util.ArrayList", [ ] ]
  },
  "stackTrace" : [ {
    "declaringClass" : "test.jackson.JacksonTest",
    "methodName" : "main",
    "fileName" : "JacksonTest.java",
    "lineNumber" : 29
  } ],
  "suppressedExceptions" : [ "java.util.ArrayList", [ ] ]
}

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

Exception in thread "main" com.fasterxml.jackson.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.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:79)
    at com.fasterxml.jackson.databind.DeserializationContext.reportUnknownProperty(DeserializationContext.java:555)
    at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:708)
    at com.fasterxml.jackson.databind.deser.std.JdkDeserializers$StackTraceElementDeserializer.deserialize(JdkDeserializers.java:414)
    at com.fasterxml.jackson.databind.deser.std.JdkDeserializers$StackTraceElementDeserializer.deserialize(JdkDeserializers.java:380)
    at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:151)
...

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

java.lang.RuntimeException: java.lang.NumberFormatException: For input string: "String"
    at .main(JacksonTest.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(JacksonTest.java:30)

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

4

6 回答 6

8

这里似乎有一个 Jackson JIRA条目Jackson 似乎无法处理declaringClassin java.lang.StackTraceElement,因为调用了与该字段对应的 getter getClassName()

StackTraceElement我按照上面提到的 JIRA 条目中的建议使用自定义包装器解决了这个问题。自定义包装器 ( CustomStackTraceElement) 将包含字段declaringClassmethodNamefileNamelineNumber以及相应的 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();
}

在序列化过程中,将通过以下方法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;
}

...并且反向转换将在反序列化期间完成:

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;
}

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

于 2013-08-08T09:14:41.417 回答
2

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

当然不要忘记

objectMapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

从迈克尔的回答。

于 2013-08-06T14:54:02.213 回答
2

添加这个:

objectMapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

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

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

我使用了以下代码:

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 ) );
    }
}

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

执行后会打印以下内容:

{
"@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\""
}
于 2013-08-06T15:44:05.537 回答
1

我有一个类似的问题。我现在正在使用这段代码,它允许我用适当的类型序列化和反序列化异常(即 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;

}

我正在操纵BeanDeserializerFactory使buildThrowableDeserializer不对待Throwable任何特殊但就像对待任何其他人一样Object。然后使用来定义我喜欢Mixins的“特殊”处理。ThrowableStackTraceElement

于 2016-09-27T12:24:29.393 回答
1

尝试使用多态性,以便杰克逊反序列化器知道要创建哪种 Throwable:

/**
 * Jackson 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 {
  }
}
于 2016-11-22T21:29:28.880 回答
0

使用json序列化有那么必要吗?看起来好像有一些带有可投掷物的错误。为什么不使用系统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();
于 2013-08-07T14:07:28.233 回答