2013-07-25 12 views
9

Ich habe Probleme beim Deserialisieren Exception und Throwable Instanzen mit Jackson (Version 2.2.1). Betrachten Sie das folgende Snippet:Probleme beim Deserialisieren von Ausnahme/throwable mit Jackson in 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(); 
    } 
} 

Der Ausgang des System.out.println im catch Block ist:

{ 
    "@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", [ ] ] 
} 

was in Ordnung zu sein scheint. Aber wenn ich dies mit objectMapper.readValue() deserialisieren versuchen, erhalte ich die folgende Ausnahme:

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) 
... 

Ich habe dann versucht mix-in annotations verwenden, declaringClass in java.lang.StackTraceElement zu ignorieren, aber jetzt ist die deserialisiert Exception enthält nicht die deklarierte Klasse in seinem Stapel Spur:

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) 

Fehle ich etwas? Jede Hilfe wird sehr geschätzt.

Antwort

4

Es scheint für diese here ein Jackson JIRA Eintrag. Jackson scheint nicht in der Lage zu sein, die declaringClass in java.lang.StackTraceElement zu behandeln, da der Getter entsprechend diesem Feld getClassName() genannt wird.

Ich behob dieses Problem, indem Sie einen benutzerdefinierten Wrapper um StackTraceElement verwenden, wie in dem oben erwähnten JIRA-Eintrag vorgeschlagen.Der benutzerdefinierte Wrapper (CustomStackTraceElement) wird die Felder declaringClass, methodName, fileName und lineNumber und die entsprechenden Getter und Setter darin enthalten. I modifiziert, um die catch Block (in der Frage erwähnt) zu sein, wie folgt:

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

Die StackTraceElement[] wird in List<CustomStackTraceElement> durch das folgende Verfahren bei der Serialisierung umgewandelt werden:

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

... und die Rückwärts

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

Jetzt, nach der Deserialisierung der: Transformation wird bei der Deserialisierung erfolgen Das Objektenthält den erwarteten Stack-Trace.

+0

Wer weiß, ob das in späteren Versionen von Jackson richtig behoben wurde ?? – Kranach

1

Es scheint, dass die Ausgabe, die Sie in Version 2.2.1 erhalten, nicht das gleiche ist, wie ich es mit Version 2.2.0 bekomme (was laut der Website die neueste Version 2.x ist). Neben der neuesten verfügbaren Version 2.x auf dem Maven Repository ist 2.2.2. Also würde ich versuchen, es entweder auf 2.2.0 herunterzustufen oder es auf 2.2.2 zu aktualisieren. Wenn eine der Änderungen das erwartete Ergebnis bringt, würde ich mit dieser Version weiter gehen und einen BUG in Jacksons JIRA öffnen.

Und natürlich nicht vergessen

objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 

von Michaels Antwort.

+0

Ich verwende Version 2.2.1 von Jackson. 'SerializationConfig.Feature' entspricht der alten Paketstruktur (vor Version 2.0). Ich habe versucht, die beiden Zeilen zu kommentieren (während der Serialisierung, Deserialisierung und den anderen möglichen Kombinationen), aber kein Glück. Das deserialisierte 'Throwable' enthält nicht die deklarierende Klasse. Und ich benutze Java 7 (das sollte aber nicht viel ausmachen). – Jackall

+0

OK, ich habe jetzt auch mit Version 2.2.0 getestet und konfiguriert wie Michael Cheremuhin gesagt und ich bekomme auch die Klassennamen in der Stack-Trace (; wenn das alles ist was du vermisst). Darf ich Sie fragen, wo Sie die Version 2.2.1 erhalten haben, da auf der Website http://wiki.fasterxml.com/JacksonDownload die letzte Version 2.2.0 steht? –

+0

Ich habe Version 2.2.1 von Jackson aus dem Central Maven Repository (http://search.maven.org/). – Jackall

2

hinzufügen:

objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 

Und der entserialisierten Ausnahme machen aus der gleichen Art und Weise, wie zum ersten Mal: ​​

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

habe ich den folgenden 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)); 
    } 
} 

Hinzugefügt diese Gläser: Jackso n-annotations-2.2.0.jar, jackson-core-2.2.0.jar und jackson-databind-2.2.0.jar.

Nach der Ausführung wird die folgende gedruckt:

{ 
"@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

Ich habe versucht, mit diesem 'DeserializationFeature', aber ich bekomme die gleiche Ausgabe wie die mit Mix-in-Annotationen (d. H. Es gibt keine deklarierende Klasse in der deserialisierten Ausgabe). Ich brauche die komplette 'Exception' /' Throwable' Information nach der Deserialisierung - ansonsten sieht es komisch und unvollständig aus. – Jackall

+0

Ich kann nicht verstehen, warum versuchen Sie, das gleiche Ergebnis mit throwable.printStackTrace() und objectMapper.writeValueAsString (LaufzeitException) zu erhalten. Führe 'e.printStackTrace()' aus und du wirst das gleiche "komisch und unvollständig" erhalten. Bitte ändern Sie die letzte Zeile Ihres Codes in 'System.out.println (objectMapper.writeValueAsString (throwable));' und überprüfe die Ausgabe. –

+0

Das Hauptproblem, mit dem ich konfrontiert bin, ist die Deserialisierung. Wenn ich 'objectMapper.writeValueAsString (runtimeException)' sage, deserialisiere ich das 'Throwable' überhaupt nicht - es ist nur die serialisierte Ausgabe. Stellen Sie sich ein Szenario vor, bei dem ich das 'Throwable' serverseitig serialisieren, die serialisierte Ausgabe (in Form einer' String') über die Leitung übertragen und dann auf der Clientseite deserialisieren muss (mit 'objectMapper.readValue') Methode). Der Client sollte in der Lage sein, nach der Deserialisierung die vollständigen Throwable-Informationen anzuzeigen. – Jackall

0

Ist es so notwendig Json Serialisierung zu benutzen? Sieht so aus, als gäbe es einige Bugs mit Throwables. Warum System api nicht:

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

Ich hatte ein ähnliches Problem. Ich bin mit diesem Code jetzt, und es erlaubt mir Ausnahmen mit dem richtigen Typen zu serialisieren und deserialisieren (dh ein RuntimeException wird ein RuntimeException wieder :) sein):

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; 

} 

ich die BeanDeserializerFactory Manipulation buildThrowableDeserializer nicht zu machen Behandeln Sie Throwable irgendein spezielles aber wie jedes andere Object. Dann mit Mixins, um die "spezielle" Handhabung von Throwable und StackTraceElement nach meinem Geschmack zu definieren.

+0

Beachten Sie, dass die anderen Zuordnungskonfigurationen für unsere Anwendung tatsächlich erforderlich sind und nicht, was in diesem Beispiel erforderlich ist –

0

Try Polymorphismus so dass jackson Deserializer weiß, welche Art von Throwable zu erstellen:

/** 
* 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 { 
    } 
} 
Verwandte Themen