Jackson ContextualDeserializer does not deserialize all fields - java

I'm implementing a custom jackson deserializer for one of my entities.
My entity is the following:
#Value
#JsonDeserialize
#AllArgsConstructor
public class TestModel {
private final FieldUpdate<UUID> field1Update;
private final FieldUpdate<UUID> field2Update;
private final FieldUpdate<String> field3Update;
public String toString() {
return "TestModel. Field1="+(field1Update != null ? field1Update.toString() : null)+
" Field2="+(field2Update != null ? field2Update.getClass().getName() : null) +
" Field3="+(field3Update != null ? field3Update.getClass().getName() : null);
}
}
My problem is that serialiation works as expected - the successfully serialized object is as follow:
{
"field1Update" : {
"type" : "update",
"value" : "f59c4ef9-52c4-4f3d-99e5-a33a13ae12f3"
},
"field2Update" : {
"type" : "keep"
},
"field3Update" : {
"type" : "reset"
}
}
=> which is correct.
(There are the 3 Types Update, Keep and Reset). Only update needs a value.
The problem is: When i deserialize this, only the first field (field1Update) gets deserialized. The other 2 fields (field2Update and field3Update) are null after deserialization completes.
My Deserializer is the following:
public class FieldUpdateDeserializer extends StdDeserializer implements ContextualDeserializer {
private JavaType contentType;
public FieldUpdateDeserializer(JavaType contentType) {
this(null,contentType);
}
public FieldUpdateDeserializer() {
this(null,null);
}
public FieldUpdateDeserializer(Class<?> vc, JavaType contentType) {
super(vc);
this.contentType = contentType;
}
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException {
JavaType t = property.getType();
JavaType boundType = t.getBindings().getBoundType(0);
return new FieldUpdateDeserializer(boundType);
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctx) throws IOException {
if(!"type".equals(jp.nextFieldName()) )throw new JsonParseException(jp,"'type' expected");
String typeVal = jp.nextTextValue();
if("update".equals(typeVal)) {
jp.nextValue(); //consume type.
try {
JsonDeserializer deser = ctx.findNonContextualValueDeserializer(contentType);
return new Update<>(deser.deserialize(jp,ctx));
} catch (Exception ex) {
throw new IllegalStateException("Could not handle deserialization for type", ex);
}
} else if("keep".equals(typeVal)) {
return new Keep<>();
} else if("reset".equals(typeVal)) {
return new Reset<>();
} else {
return ctx.handleUnexpectedToken(FieldUpdate.class, jp);
}
}
}
An interesting fact is that jackson calls the deserialize(...) method only one time and i can't figure out why....
Glad if somebody can drop me a hint.
greetings,
Michael

Ok - after some sleep and analyzing what happens in the jackson serializer, i discovered that i did not consume enough tokens in my deserializer.
The working version for my deserializer is:
public Object deserialize(JsonParser jp, DeserializationContext ctx) throws IOException {
if(!"type".equals(jp.nextFieldName()) )throw new JsonParseException(jp,"'type' expected");
String typeVal = jp.nextTextValue();
if("update".equals(typeVal)) {
jp.nextValue(); //consume type.
try {
JsonDeserializer deser = ctx.findNonContextualValueDeserializer(contentType);
return new Update<>(deser.deserialize(jp,ctx));
} catch (Exception ex) {
throw new IllegalStateException("Could not handle deserialization for type", ex);
} finally {
jp.nextToken();
}
} else if("keep".equals(typeVal)) {
jp.nextToken();
return new Keep<>();
} else if("reset".equals(typeVal)) {
jp.nextToken();
return new Reset<>();
} else {
return ctx.handleUnexpectedToken(FieldUpdate.class, jp);
}
}

Related

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "type" - how do i get the no. of times this exception is thrown

I am fetching the JSON response from a URL and this has property names misspelt. In this case, UnrecognizedPropertyException is thrown along with the propertyName. How do i keep track of the property name along with the count of the times the property is misspelt. Below is the piece of code:
ObjectMapper objectMapper = new ObjectMapper();
int counter = 0;
JsonContainer[] jc = null;
URL url = new URL("sample_url");
try {
jc = objectMapper.readValue(url, JsonContainer[].class);
}
catch(UnrecognizedPropertyException e) {
counter++;
e.getPropertyName();
}
Here the counter always return 1 though a property name is misspelt more than once. Also, how do i fetch the property name from the exception thrown
If you want to skip unknown properties, but gather the names and counts of missing properties, you can supply your own implementation of DefaultDeserializationContext, overriding the reportUnknownProperty(...) method, e.g.
public final class ReportingDeserializationContext extends DefaultDeserializationContext {
private static final long serialVersionUID = 1L;
public ReportingDeserializationContext() {
super(BeanDeserializerFactory.instance, null);
}
private ReportingDeserializationContext(ReportingDeserializationContext src, DeserializationConfig config, JsonParser jp, InjectableValues values) {
super(src, config, jp, values);
}
private ReportingDeserializationContext(ReportingDeserializationContext src, DeserializerFactory factory) {
super(src, factory);
}
#Override
public DefaultDeserializationContext createInstance(DeserializationConfig config, JsonParser jp, InjectableValues values) {
return new ReportingDeserializationContext(this, config, jp, values);
}
#Override
public DefaultDeserializationContext with(DeserializerFactory factory) {
return new ReportingDeserializationContext(this, factory);
}
#Override
public void reportUnknownProperty(Object instanceOrClass, String fieldName, JsonDeserializer<?> deser) throws JsonMappingException {
Class<?> clazz = (instanceOrClass instanceof Class ? (Class<?>) instanceOrClass : instanceOrClass.getClass());
System.out.println("Unknown Property: " + clazz.getName() + "." + fieldName);
}
}
Test
public class Test {
public int a;
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper(null, null, new ReportingDeserializationContext());
Test jc = objectMapper.readValue("{ \"a\": 10, \"b\": 11, \"c\": 12 }", Test.class);
System.out.println(jc.a);
}
}
Output
Unknown Property: Test.b
Unknown Property: Test.c
10
I'll leave it to you to record and count the unknown properties, instead of just printing them, like the example above.

Java to Json validation using GSON

While converting Java object to Json string using GSON API, I also want to fail this Json conversion if any of the annotated attribute is null.
For example
public class Order{
#SerializedName("orderId")
#Expose
#Required
private Integer id;
//getter & setter available for id
}
Now as I am doing
Order order = new Order();
JSONObject jsonobj = new JSONObject(gson.toJson(order));
I want to fail the above Java to Json transformation if any of the #Required attribute is null
Is this possible using GSON?
I wanted to fail Java to Json conversion, if any of the Java attribute is null which is annotated as #Required,
I am able to achieve this using following approach. Please let me know if you see any issues:
class RequiredKeyAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter out, T value) throws IOException {
if (value != null) {
Field[] fields = value.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (fields[i]
.isAnnotationPresent(Required.class)) {
validateNullValue(value, fields[i]);
}
}
}
delegate.write(out, value);
}
private <T> void validateNullValue(T value, Field field) {
field.setAccessible(true);
Class t = field.getType();
Object v = null;
try {
v = field.get(value);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalArgumentException(e);
}
if (t == boolean.class && Boolean.FALSE.equals(v)) {
throw new IllegalArgumentException(field + " is null");
} else if (t.isPrimitive()
&& ((Number) v).doubleValue() == 0) {
throw new IllegalArgumentException(field + " is null");
} else if (!t.isPrimitive() && v == null) {
throw new IllegalArgumentException(field + " is null");
}
}
#Override
public T read(JsonReader in) throws IOException {
return delegate.read(in);
}
};
}
}
RequiredKeyAdapterFactory requiredKeyAdapterFactory = new RequiredKeyAdapterFactory();
Gson gson = new GsonBuilder().registerTypeAdapterFactory(requiredKeyAdapterFactory)
.create();
This is working

How to deserialize a float value with a localized decimal separator with Jackson

The input stream I am parsing with Jackson contains latitude and longitude values such as here:
{
"name": "product 23",
"latitude": "52,48264",
"longitude": "13,31822"
}
For some reason the server uses commas as the decimal separator which produces an InvalidFormatException. Since I cannot change the server output format I would like to teach Jackson's ObjectMapper to handle those cases. Here is the relevant code:
public static Object getProducts(final String inputStream) {
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.readValue(inputStream,
new TypeReference<Product>() {}
);
} catch (UnrecognizedPropertyException e) {
e.printStackTrace();
} catch (InvalidFormatException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (JsonParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
And here is the POJO:
import com.fasterxml.jackson.annotation.JsonProperty;
public class Product {
#JsonProperty("name")
public String name;
#JsonProperty("latitude")
public float latitude;
#JsonProperty("longitude")
public float longitude;
}
How can I tell Jackson that those coordinate values come with a German locale?
I suppose a custom deserializer for the specific fields as discussed here would be the way to go. I drafted this:
public class GermanFloatDeserializer extends JsonDeserializer<Float> {
#Override
public Float deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
// TODO Do some comma magic
return floatValue;
}
}
Then the POJO would look like this:
import com.fasterxml.jackson.annotation.JsonProperty;
public class Product {
#JsonProperty("name")
public String name;
#JsonDeserialize(using = GermanFloatDeserializer.class, as = Float.class)
#JsonProperty("latitude")
public float latitude;
#JsonDeserialize(using = GermanFloatDeserializer.class, as = Float.class)
#JsonProperty("longitude")
public float longitude;
}
I came up with the following solution:
public class FlexibleFloatDeserializer extends JsonDeserializer<Float> {
#Override
public Float deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
String floatString = parser.getText();
if (floatString.contains(",")) {
floatString = floatString.replace(",", ".");
}
return Float.valueOf(floatString);
}
}
...
public class Product {
#JsonProperty("name")
public String name;
#JsonDeserialize(using = FlexibleFloatDeserializer.class)
#JsonProperty("latitude")
public float latitude;
#JsonDeserialize(using = FlexibleFloatDeserializer.class)
#JsonProperty("longitude")
public float longitude;
}
Still I wonder why I it does not work when I specify the return value class as as = Float.class as can be found in the documentation of JsonDeserialize. It reads as if I am supposed to use one or the other but not both. Whatsoever, the docs also claim that as = will be ignored when using = is defined:
if using() is also used it has precedence (since it directly specified deserializer, whereas this would only be used to locate the deserializer) and value of this annotation property is ignored.
With all respect to accepted answer, there is a way to get rid of those #JsonDeserialize annotations.
You need to register the custom deserializer in the ObjectMapper.
Following the tutorial from official web-site you just do something like:
ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule(
"DoubleCustomDeserializer",
new com.fasterxml.jackson.core.Version(1, 0, 0, null))
.addDeserializer(Double.class, new JsonDeserializer<Double>() {
#Override
public Double deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
String valueAsString = jp.getValueAsString();
if (StringUtils.isEmpty(valueAsString)) {
return null;
}
return Double.parseDouble(valueAsString.replaceAll(",", "\\."));
}
});
mapper.registerModule(testModule);
If you're using Spring Boot there is a simpler method. Just define the Jackson2ObjectMapperBuilder bean somewhere in your Configuration class:
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.deserializerByType(Double.class, new JsonDeserializer<Double>() {
#Override
public Double deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
String valueAsString = jp.getValueAsString();
if (StringUtils.isEmpty(valueAsString)) {
return null;
}
return Double.parseDouble(valueAsString.replaceAll(",", "\\."));
}
});
builder.applicationContext(applicationContext);
return builder;
}
and add the custom HttpMessageConverter to the list of WebMvcConfigurerAdapter message converters:
messageConverters.add(new MappingJackson2HttpMessageConverter(jacksonBuilder().build()));
A more general solution than the other proposed answers, which require registering individual deserializers for each type, is to provide a customized DefaultDeserializationContext to ObjectMapper.
The following implementation (which is inspired by DefaultDeserializationContext.Impl) worked for me:
class LocalizedDeserializationContext extends DefaultDeserializationContext {
private final NumberFormat format;
public LocalizedDeserializationContext(Locale locale) {
// Passing `BeanDeserializerFactory.instance` because this is what happens at
// 'jackson-databind-2.8.1-sources.jar!/com/fasterxml/jackson/databind/ObjectMapper.java:562'.
this(BeanDeserializerFactory.instance, DecimalFormat.getNumberInstance(locale));
}
private LocalizedDeserializationContext(DeserializerFactory factory, NumberFormat format) {
super(factory, null);
this.format = format;
}
private LocalizedDeserializationContext(DefaultDeserializationContext src, DeserializationConfig config, JsonParser parser, InjectableValues values, NumberFormat format) {
super(src, config, parser, values);
this.format = format;
}
#Override
public DefaultDeserializationContext with(DeserializerFactory factory) {
return new LocalizedDeserializationContext(factory, format);
}
#Override
public DefaultDeserializationContext createInstance(DeserializationConfig config, JsonParser parser, InjectableValues values) {
return new LocalizedDeserializationContext(this, config, parser, values, format);
}
#Override
public Object handleWeirdStringValue(Class<?> targetClass, String value, String msg, Object... msgArgs) throws IOException {
// This method is called when default deserialization fails.
if (targetClass == float.class || targetClass == Float.class) {
return parseNumber(value).floatValue();
}
if (targetClass == double.class || targetClass == Double.class) {
return parseNumber(value).doubleValue();
}
// TODO Handle `targetClass == BigDecimal.class`?
return super.handleWeirdStringValue(targetClass, value, msg, msgArgs);
}
// Is synchronized because `NumberFormat` isn't thread-safe.
private synchronized Number parseNumber(String value) throws IOException {
try {
return format.parse(value);
} catch (ParseException e) {
throw new IOException(e);
}
}
}
Now set up your object mapper with your desired locale:
Locale locale = Locale.forLanguageTag("da-DK");
ObjectMapper objectMapper = new ObjectMapper(null,
null,
new LocalizedDeserializationContext(locale));
If you use Spring RestTemplate, you can set it up to use objectMapper like so:
RestTemplate template = new RestTemplate();
template.setMessageConverters(
Collections.singletonList(new MappingJackson2HttpMessageConverter(objectMapper))
);
Note that the value must be represented as a string in the JSON document (i.e. {"number": "2,2"}), since e.g. {"number": 2,2} is not valid JSON and will fail to parse.

Jackson json deserialization, with root element from json

I am having a question with Jackson that I think should be simple to solve, but it is killing me.
I have a java POJO class that looks like this (with getters and setters)
#JsonRootName(value = "notificacion")
public class NotificacionDTO extends AbstractDTO {
#JsonProperty(required=true)
private Integer instalacion;
#JsonProperty(required=true)
private Integer tipoNotificacion;
#JsonProperty(required=true)
private String mensaje;
}
Here is the AbstractDTO
public abstract class AbstractDTO implements Serializable {
public void validate() {
Field[] declaredFields = this.getClass().getDeclaredFields();
for (Field field : declaredFields) {
if (field.isAnnotationPresent(JsonProperty.class)){
if (field.getAnnotation(JsonProperty.class).required() && this.isNullParameter(field)) {
throw new RuntimeException(String.format("El parametro %s es null o no esta presente en el JSON.", field.getName()));
}
}
}
}
private boolean isNullParameter(Field field) {
try {
field.setAccessible(true);
Object value = field.get(this);
if (value == null) {
return true;
} else if (field.getType().isAssignableFrom(String.class)) {
return ((String) value).isEmpty();
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return false;
}
}
I want to deserialize JSON that looks like this into a NotificacionDTO object:
{
"notificacion":
{
"instalacion":"1",
"tipoNotificacion":"2",
"mensaje":"Un Mensaje"
}
}
This is my EndPoint
#Controller
#RequestMapping("/notificacion")
public class NotificacionEndPoint extends AbstractEndPoint{
#Autowired
private NotificacionService service;
#RequestMapping(value = {"", "/"}, method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void addNotification(#RequestBody NotificacionDTO notification) throws JsonParseException, JsonMappingException, IOException {
this.log.info("[POST RECEIVED] = " + notification);
notification.validate();
this.service.addNotification(notification);
}
}
I hava a custom ObjectMapper with this
public class JsonObjectMapper extends ObjectMapper {
public JsonObjectMapper() {
super();
this.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
}
}
When i POST i'm getting this error
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "notificacion" (class ar.com.tecnoaccion.services.notificacion.NotificacionDTO), not marked as ignorable (3 known properties: , "tipoNotificacion", "instalacion", "mensaje"])
at [Source: org.apache.catalina.connector.CoyoteInputStream#1c1f3f7; line: 3, column: 5] (through reference chain: ar.com.tecnoaccion.services.notificacion.NotificacionDTO["notificacion"])
I try to add this to my DTO
#JsonIgnoreProperties(ignoreUnknown = true)
but when i validate my DTO with the method validate all the dto's attributes are null and i get this error:
java.lang.RuntimeException: El parametro instalacion es null o no esta presente en el JSON.
i am using jackson 2.2.3 and spring 3.2.1
thank you.
the simple answer is to post
{
"instalacion":"1",
"tipoNotificacion":"2",
"mensaje":"Un Mensaje"
}
instead of
{
"notificacion":
{
"instalacion":"1",
"tipoNotificacion":"2",
"mensaje":"Un Mensaje"
}
}

Jackson deserialization error handling

My problem is fairly simple : I have the following simple class:
public class Foo {
private int id = -1;
public void setId(int _id){ this.id = _id; }
public int getId(){ return this.id; }
}
And I am trying to process following JSON:
{
"id": "blah"
}
Obviously, there is a problem here ("blah" cannot be parsed to an int)
Formerly, Jackson throws something like org.codehaus.jackson.map.JsonMappingException: Can not construct instance of java.lang.Integer from String value 'blah': not a valid Integer value
I agree with this, but I'd like to register something somewhere allowing to ignore this type of mapping errors.
I tried with a DeserializationProblemHandler registered (see here) but it seems to only work on unknown properties and not deserialization problems.
Have you any clue on this issue?
I succeeded to solve my problem, thanks to Tatu from Jackson ML.
I had to use custom non blocking deserializers for every primitive types handled in Jackson.
Something like this factory :
public class JacksonNonBlockingObjectMapperFactory {
/**
* Deserializer that won't block if value parsing doesn't match with target type
* #param <T> Handled type
*/
private static class NonBlockingDeserializer<T> extends JsonDeserializer<T> {
private StdDeserializer<T> delegate;
public NonBlockingDeserializer(StdDeserializer<T> _delegate){
this.delegate = _delegate;
}
#Override
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
try {
return delegate.deserialize(jp, ctxt);
}catch (JsonMappingException e){
// If a JSON Mapping occurs, simply returning null instead of blocking things
return null;
}
}
}
private List<StdDeserializer> jsonDeserializers = new ArrayList<StdDeserializer>();
public ObjectMapper createObjectMapper(){
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule customJacksonModule = new SimpleModule("customJacksonModule", new Version(1, 0, 0, null));
for(StdDeserializer jsonDeserializer : jsonDeserializers){
// Wrapping given deserializers with NonBlockingDeserializer
customJacksonModule.addDeserializer(jsonDeserializer.getValueClass(), new NonBlockingDeserializer(jsonDeserializer));
}
objectMapper.registerModule(customJacksonModule);
return objectMapper;
}
public JacksonNonBlockingObjectMapperFactory setJsonDeserializers(List<StdDeserializer> _jsonDeserializers){
this.jsonDeserializers = _jsonDeserializers;
return this;
}
}
Then calling it like this way (pass as deserializers only those you want to be non blocking) :
JacksonNonBlockingObjectMapperFactory factory = new JacksonNonBlockingObjectMapperFactory();
factory.setJsonDeserializers(Arrays.asList(new StdDeserializer[]{
// StdDeserializer, here, comes from Jackson (org.codehaus.jackson.map.deser.StdDeserializer)
new StdDeserializer.ShortDeserializer(Short.class, null),
new StdDeserializer.IntegerDeserializer(Integer.class, null),
new StdDeserializer.CharacterDeserializer(Character.class, null),
new StdDeserializer.LongDeserializer(Long.class, null),
new StdDeserializer.FloatDeserializer(Float.class, null),
new StdDeserializer.DoubleDeserializer(Double.class, null),
new StdDeserializer.NumberDeserializer(),
new StdDeserializer.BigDecimalDeserializer(),
new StdDeserializer.BigIntegerDeserializer(),
new StdDeserializer.CalendarDeserializer()
}));
ObjectMapper om = factory.createObjectMapper();
You might want to let your controller handle the problem by adding a method that handles this specific exception
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseBody
public String handleHttpMessageNotReadableException(HttpMessageNotReadableException ex)
{
JsonMappingException jme = (JsonMappingException) ex.getCause();
return jme.getPath().get(0).getFieldName() + " invalid";
}
Of course, the line
JsonMappingException jme = (JsonMappingException) ex.getCause();
might throw a class cast exception for some cases but i haven't encountered them yet.
I have written a simple error handler which will give you some kind of error which you can return to user with bad request as status code. Use #JsonProperty required = true to get error related to missing properties. Jackson version 2.9.8.
public class JacksonExceptionHandler {
public String getErrorMessage(HttpMessageNotReadableException e) {
String message = null;
boolean handled = false;
Throwable cause = e.getRootCause();
if (cause instanceof UnrecognizedPropertyException) {
UnrecognizedPropertyException exception = (UnrecognizedPropertyException) cause;
message = handleUnrecognizedPropertyException(exception);
handled = true;
}
if (cause instanceof InvalidFormatException) {
InvalidFormatException exception = (InvalidFormatException) cause;
message = handleInvalidFormatException(exception);
handled = true;
}
if (cause instanceof MismatchedInputException) {
if (!handled) {
MismatchedInputException exception = (MismatchedInputException) cause;
message = handleMisMatchInputException(exception);
}
}
if (cause instanceof JsonParseException) {
message = "Malformed json";
}
return message;
}
private String handleInvalidFormatException(InvalidFormatException exception) {
String reference = null;
if (!exception.getPath().isEmpty()) {
String path = extractPropertyReference(exception.getPath());
reference = removeLastCharacter(path);
}
Object value = exception.getValue();
return "Invalid value '" + value + "' for property : " + reference;
}
private String handleUnrecognizedPropertyException(UnrecognizedPropertyException exception) {
String reference = null;
if (!exception.getPath().isEmpty()) {
String path = extractPropertyReference(exception.getPath());
reference = removeLastCharacter(path);
}
return "Unknown property : '" + reference + "'";
}
private String handleMisMatchInputException(MismatchedInputException exception) {
String reference = null;
if (!exception.getPath().isEmpty()) {
reference = extractPropertyReference(exception.getPath());
}
String property = StringUtils.substringBetween(exception.getLocalizedMessage(), "'", "'");
// if property missing inside nested object
if (reference != null && property!=null) {
return "Missing property : '" + reference + property + "'";
}
// if invalid value given to array
if(property==null){
return "Invalid values at : '"+ reference +"'";
}
// if property missing at root level
else return "Missing property : '" + property + "'";
}
// extract nested object name for which property is missing
private String extractPropertyReference(List<JsonMappingException.Reference> path) {
StringBuilder stringBuilder = new StringBuilder();
path.forEach(reference -> {
if(reference.getFieldName() != null) {
stringBuilder.append(reference.getFieldName()).append(".");
// if field is null means it is array
} else stringBuilder.append("[].");
}
);
return stringBuilder.toString();
}
// remove '.' at the end of property path reference
private String removeLastCharacter(String string) {
return string.substring(0, string.length() - 1);
}
}
and call this class object in global advice like this
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String message = new JacksonExceptionHandler().generator.getErrorMessage(ex);
if(message == null){
return ResponseEntity.badRequest().body("Malformed json");
}
return ResponseEntity.badRequest().body(message);
}
Create a simple Mapper:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JSONProcessingErroMapper implements ExceptionMapper<InvalidFormatException> {
#Override
public Response toResponse(InvalidFormatException ex) {
return Response.status(400)
.entity(new ClientError("[User friendly message]"))
.type(MediaType.APPLICATION_JSON)
.build();
}
}
DeserializationProblemHandler now has a lot more methods, such as handleUnexpectedToken and handleWeird*Value. It should be able to handle anything one needs.
Subclass it, override methods you need, then add it to your ObjectMapper with addHandler(DeserializationProblemHandler h).

Categories