Graceful Error Handling/Collection on Databind (2.9.0.pr2)

708 views
Skip to first unread message

Andrew Joseph

unread,
Apr 20, 2017, 4:28:45 PM4/20/17
to jackson-user

I'm looking to simply return null and add errors to a Map<JsonPointer,Exception> from all JsonDeserializer<?> instances when a JsonMappingException is encountered (since the JSON parser can continue). In each of my beans, I plan to use @JacksonInject to inject the JsonPointer for the given bean which I can then use to access the exceptions from the map once the deserialization is fully complete. 

I know this functionality is somewhere on it's way to being incorporated into DeserializationProblemHandler as stated here https://github.com/FasterXML/jackson-databind/issues/1196

...but in the meantime I'm looking for an interim solution catch and recover from all JsonMappingExceptions that can work with Jackson as it stands. 

My first attempt was using BeanDeserializerModifier figuring that I could simply wrap all deserializers like so:

   
 import com.fasterxml.jackson.core.JsonParser;
   
import com.fasterxml.jackson.core.JsonProcessingException;
   
import com.fasterxml.jackson.databind.BeanDescription;
   
import com.fasterxml.jackson.databind.DeserializationConfig;
   
import com.fasterxml.jackson.databind.DeserializationContext;
   
import com.fasterxml.jackson.databind.JsonDeserializer;
   
import com.fasterxml.jackson.databind.JsonMappingException;
   
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
   
   
import java.io.IOException;
   
   
public class ErrorHandlingDeserializerModifier extends BeanDeserializerModifier {
   
     
@Override
     
public JsonDeserializer<?> modifyDeserializer(final DeserializationConfig config, final BeanDescription beanDesc, final JsonDeserializer<?> deserializer) {
       
JsonDeserializer<?> jdesc = super.modifyDeserializer(config, beanDesc, deserializer);
       
       
return new JsonDeserializer<Object>() {
         
@Override
         
public Object deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
           
try {
             
return jdesc.deserialize(p,ctxt);
           
} catch (JsonMappingException jme) {
             
//add errors to map
             
...
             
return null;
           
}
         
}
       
};
     
}
   
}


However when I go to desalinize a bean property I run into the following error:

com.fasterxml.jackson.databind.exc.MismatchedInputException: No _valueDeserializer assigned

 at
[Source: UNKNOWN; line: -1, column: -1] (through reference chain: Jurisdiction["id"])
 at com
.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:62)
 at com
.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1294)
 at com
.fasterxml.jackson.databind.deser.impl.FailingDeserializer.deserialize(FailingDeserializer.java:29)
 at com
.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:124)
 at com
.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:275)
 at com
.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:139)
 at com
.company.ErrorHandlingDeserializerModifier$1.deserialize(ErrorHandlingDeserializerModifier.java:25)
 at com
.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3666)
 at com
.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:3589)




Tatu Saloranta

unread,
Apr 20, 2017, 8:59:16 PM4/20/17
to jackson-user
On Thu, Apr 20, 2017 at 1:28 PM, Andrew Joseph <ap.jos...@gmail.com> wrote:
>
> I'm looking to simply return null and add errors to a
> Map<JsonPointer,Exception> from all JsonDeserializer<?> instances when a
> JsonMappingException is encountered (since the JSON parser can continue). In
> each of my beans, I plan to use @JacksonInject to inject the JsonPointer for
> the given bean which I can then use to access the exceptions from the map
> once the deserialization is fully complete.

I understand the idea (and it makes sense), but unfortunately this
approach is all but guaranteed not to work for many cases: if
exception is thrown, state of databinding is unlikely to be such that
further processing would succeed.

However, if you want to try it and see how far you can get, I think I
can help with specific problem here:
Most likely this is because you really need to delegate
`createContextual()` call of `ContextualDeserializer` (and possibly
`resolve()` of `ResolvableDeserializer`).
You may want to have a look at `StdDelegatingDeserializer` to see what
it does: basically some of initialization is deferred to resolve
problems like cyclic types.
And you probably want to extend `StdDeserializer` instead of directly
implementing `JsonDeserializer`.

Now... as to what I think might be better approach (or complementary
one): registering `DeserializationProblemHandler` would allow you to
handle subset of problems in much safer way.
Callbacks allow you to return `null` (or some other valid
placeholder), and denote information about type of failure.

If you do this I would also be interested in learning about
experiences, and if there are other types of recoverable (to the
degree errors are collected at least) failures that do not yet go
through that handler abstraction: work for adding new methods started
in 2.8 (before that, only "unknown property" handler existed) and
continues with 2.9.

-+ Tatu +-

Andrew Joseph

unread,
Apr 21, 2017, 6:06:11 PM4/21/17
to jackson-user
Actually looks like DeserializationProblemHandler handles most of what I want after all! I'm really only focused on collecting and recovering from errors made by the user submitting the json -not my own incompetence as a developer. 
handleWeirdStringValue and handleWeirdNumberValue seem to recover properly on their own. handleUnexpectedToken seems to only require parser.skipChildren() to proceed to the next value. 

Only thing I cannot seem to find is a dedicated method to reset the parser to where it began parsing when the deserializer was called -in order to be able to skip over the problem object/array and continue deserializing -especially in cases where you are delving deep into a nested object for a type deserializer. Converting geoJSON to JTS geometry is the most obvious and common case that comes to mind. 

For example, say I have the following bean:

public class LameBeanOfUncreativeness {
 
private UUID id;
 
private String name;
 
//geoJson see https://github.com/bedatadriven/jackson-datatype-jts
 
private com.vividsolutions.jts.geom.MultiPolygon
 
private String anotherWorthlessString;

 
...getters/setters
}

JSON looks like 

{
 
"id" : "b644ed02-26dd-11e7-93ae-92361f002671",
  "name" : "name",
 
"geometry" : {
   
"type": "MultiPolygon",
   
"coordinates": [
     
[
       
[
         
[
           
-111.78458342744,
           
40.6768905586
         
],
         
[
           
-111.784777398489,
           
40.676681591667
         
],
         
[
           
-111.784794225842,
           
40.6766635501376
         
],
         
[
           
-111.784813388878,
           
40.6766428802297
         
],
         
[
           
-111.785118238289,
           
"Lenin wanted to destroy this deserialization operation, and that’s my goal too"
         
],
         
[
           
-111.785092595041,
           
40.6768713732734
         
],
         
[
           
-111.785068477072,
           
40.6768954906377
         
],
         
[
           
-111.785045942486,
           
40.6769204799231
         
],
         
[
           
-111.785025045571,
           
40.6769462809292
         
],
         
[
           
-111.78500583667,
           
40.6769728314999
         
],
         
[
           
-111.784990180876,
           
40.6769970682503
         
],
         
[
           
-111.784959647803,
           
40.6770470934933
         
],
         
[
           
-111.78458342744,
           
40.6768905586
         
]
       
]
     
]
   
]
 
},
 
"anotherWorthlessString" : "Another Worthless Value"
}

I want to ensure that weirdStringValue throws an exception at the improper cordinate -and that the exception is passed up the deserialization chain up to the root "geometry" deserializer -where I will then set the value of "multipolygon" to null and add the exception to the exceptions map and continue parsing from the next property token, so that the value of  "anotherWorthlessString "is captured.

Tatu Saloranta

unread,
Apr 21, 2017, 11:53:46 PM4/21/17
to jackson-user
On Fri, Apr 21, 2017 at 3:06 PM, Andrew Joseph <ap.jos...@gmail.com> wrote:
> Actually looks like DeserializationProblemHandler handles most of what I
> want after all! I'm really only focused on collecting and recovering from
> errors made by the user submitting the json -not my own incompetence as a
> developer.
> handleWeirdStringValue and handleWeirdNumberValue seem to recover properly
> on their own. handleUnexpectedToken seems to only require
> parser.skipChildren() to proceed to the next value.
>
> Only thing I cannot seem to find is a dedicated method to reset the parser
> to where it began parsing when the deserializer was called -in order to be
> able to skip over the problem object/array and continue deserializing

Since tokens are processing streaming manner, and no buffering is done
(except for specific limited cases, where reordering is needed), this
is not really possible.
DeserializationProblemHandler methods are called in expected place(s)
and are responsible for trying to keep token stream in compatible
state: this is why `skipChildren()` is called for some of the cases.
Throwing exceptions generally cuts through call hierarchy and tends to
make nested deserializers get off expected state.

So choices tend to be to make handler swallow unusable content, but
this can only be done for leaf level.

I guess it might be interesting to have an exception type that could
be used as signal from child deserializer to parent one... although
even then managing of token stream nesting is tricky.

-+ Tatu +-
> --
> You received this message because you are subscribed to the Google Groups
> "jackson-user" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to jackson-user...@googlegroups.com.
> To post to this group, send email to jackso...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages