2013-07-25 3 views
9

Я сталкиваюсь с проблемами при десериализации Exception и Throwable экземпляров с использованием Jackson (версия 2.2.1). Рассмотрим следующий фрагмент кода:Проблемы при десериализации исключения/срабатывания с использованием Jackson в Java

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: [email protected]; 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, игнорировать declaringClass в 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

Там, кажется, запись Джексона JIRA для этого here. Джексон, похоже, не может обрабатывать declaringClass в java.lang.StackTraceElement, так как геттер, соответствующий этому полю, называется getClassName().

Я исправил эту проблему, используя специальную оболочку около StackTraceElement, как указано в записи JIRA, упомянутой выше.Пользовательская обертка (CustomStackTraceElement) будет иметь поля declaringClass, methodName, fileName и lineNumber и соответствующие геттеры и сеттеры в нем. Я изменил 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; 
} 

Теперь, после десериализацииОбъектимеет ожидаемую трассировку стека.

+0

Кто-нибудь знает, исправлено ли это в более поздних версиях Джексона ?? – Kranach

1

Похоже, что вы получаете версию 2.2.1, это не то же самое, что я получаю с версией 2.2.0 (которая, согласно веб-сайту, является последней версией 2.x). Кроме того, последняя версия версии 2.x в репозитории Maven - 2.2.2. Поэтому я постараюсь либо понизить его до 2.2.0, либо обновить до 2.2.2. Если какое-либо из изменений приведет к ожидаемому результату, я продолжу эту версию и открою BUG в JIRA Джексона.

И, конечно же, не забудьте

objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 

от ответа Майкла.

+0

Я использую версию 2.2.1 Джексона. 'SerializationConfig.Feature' соответствует старой структуре пакета (до версии 2.0). Я попытался прокомментировать две строки, как вы предполагали (во время сериализации, десериализации и других возможных комбинаций), но не повезло. В десериализованном «Throwable» не содержится объявляющий класс. И я использую Java 7 (это не имеет большого значения, хотя). – Jackall

+0

ОК, я тестировал теперь также версию 2.2.0 и настроен, как сказал Майкл Черемухин, и я также получаю имена классов в трассировке стека (если это все, что вы пропустили). Могу ли я спросить вас, где вы получили версию 2.2.1, так как на веб-сайте http://wiki.fasterxml.com/JacksonDownload написано, что последняя версия 2.2.0? –

+0

Я получил версию 2.2.1 Джексона из Центрального хранилища Maven (http://search.maven.org/). – Jackall

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

Добавлены следующие банки: jackso n-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\"" 
} 
+0

Я попытался использовать этот «DeserializationFeature», но получаю тот же результат, что и с аннотациями микширования (т. Е. На десериализованном выходе нет объявляющего класса). Мне нужна полная информация об исключении/«Throwable» после ее десериализации - это выглядит странно и неполно. – Jackall

+0

Я не могу понять, почему вы пытаетесь получить тот же результат с помощью throwable.printStackTrace() и objectMapper.writeValueAsString (runtimeException). Выполните 'e.printStackTrace()', и вы получите то же самое «странное и неполное». Пожалуйста, измените последнюю строку вашего кода на «System.out.println (objectMapper.writeValueAsString (throwable)); и проверьте выход. –

+0

Основная проблема, с которой я столкнулся, - это десериализация. Если я говорю 'objectMapper.writeValueAsString (runtimeException)', я вообще не десериализую «Throwable» - это всего лишь сериализованный вывод. Рассмотрим сценарий, где мне нужно сериализовать «Throwable» на стороне сервера, передать сериализованный вывод (в форме «String») по проводу и затем десериализовать его на стороне клиента (используя 'objectMapper.readValue' метод). Клиент должен иметь возможность просматривать полную информацию «Throwable» после ее десериализации. – Jackall

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

У меня была аналогичная проблема. Я использую этот код сейчас, и это позволяет мне сериализации и десериализации исключения с надлежащими типами (т.е. RuntimeException будет RuntimeException снова :)):

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 для определения «специальной» обработки Throwable и StackTraceElement по моему вкусу.

+0

Обратите внимание, что другие конфигурации сопоставления - это то, что нам действительно нужно для нашего приложения, а не то, что требуется в этом примере –

0

Попробуйте использовать полиморфизм так, что джексон десериализатор знает, какое 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 { 
    } 
}