Marcos J.C Kichel
Marcos J.C Kichel

Reputation: 7219

Jackson JsonDeserialize not being called for @QueryParam

I have mapped a custom deserializer to convert Strings on dd/MM/yyyy pattern to LocalDate so I can call my services with a more readable signature..

This is my dto class that is used as a Jersey @BeanParam to transport data between layers:

public class ProdutoFilterDto implements FilterDto {

private static final long serialVersionUID = -4998167328470565406L;

@QueryParam("dataInicial")
@JsonDeserialize(using = CustomLocalDateDeserializer.class)
private LocalDate dataInicial;

@QueryParam("dataInicial")
@JsonDeserialize(using = CustomLocalDateDeserializer.class)
private LocalDate dataFinal;

public LocalDate getDataInicial() {
    return dataInicial;
}

public void setDataInicial(LocalDate dataInicial) {
    this.dataInicial = dataInicial;
}

public LocalDate getDataFinal() {
    return dataFinal;
}

public void setDataFinal(LocalDate dataFinal) {
    this.dataFinal = dataFinal;
}

}

and this is my custom deserializer:

public class CustomLocalDateDeserializer extends JsonDeserializer<LocalDate> {

@Override
public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
    final String data = p.getValueAsString();
    return (LocalDate) formatter.parse(data);
}

}

Its being used on this jersey service:

@Path("produto")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ProdutoService {

...

@GET
@Path("query")
@Override
public Response query(
        @QueryParam("offset") @DefaultValue(value = "0") Integer offSet, 
        @QueryParam("limit") @DefaultValue(value = "10") Integer limit, 
        @BeanParam ProdutoFilterDto filter) { ... }

...

}

I am calling like this:

${host goes here}/produto/query?dataInicial=11/09/1992

The problem is that the deserializer method is never called and the bean param variable remains null..

Upvotes: 4

Views: 5570

Answers (1)

Paul Samsotha
Paul Samsotha

Reputation: 209004

MessageBodyReaders aren't used for @QueryParam. You seem to be expecting the Jackson MessageBodyReader to handle this deserialization, but it doesn't work like that.

Instead you will want to use a ParamConverter, which will need to be registered through a ParamConverterProvider. For example:

@Provider
public class LocalDateParamConverterProvider implements ParamConverterProvider {

    final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");

    @Override
    public <T> ParamConverter<T> getConverter(
            Class<T> rawType, Type genericType, Annotation[] antns) {

        if (LocalDate.class == rawType) {
            return new ParamConverter<T>() {
                @Override
                public T fromString(String string) {
                    try {
                        LocalDate localDate = LocalDate.parse(string, formatter);
                        return rawType.cast(localDate);
                    } catch (Exception ex) {
                        throw new BadRequestException(ex);
                    }
                }

                @Override
                public String toString(T t) {
                    LocalDate localDate = (LocalDate) t;
                    return formatter.format(localDate);
                }
            };
        }

        return null;
    }
}

Now LocalDate will work with @QueryParam and other @XxxParams also.

Some things to note:

  • If your goal is to parse both your @XxxParams and your JSON body into a bean this will not work. I'm not sure how that would work, but I'm sure it would involve a lot of hacking, and I wouldn't recommend it.

  • Your cast to (LocalDate) won't work. It's an illegal cast to java.time.format.Parsed. See correct way in code example.

  • Related to the above point. I was pulling out my hair for a good hour trying to figure out why I was getting a 404, using your parse code. With a 404, the last place I thought to look was in the ParamConverter. But it seems any uncaught exceptions that are thrown in the ParamConverter, will cause a 404. Doesn't make much sense right? The head pounding led me to this, which led me to this, which seems to be a poor specification

    "if the field or property is annotated with @MatrixParam, @QueryParam or @PathParam then an implementation MUST generate an instance of NotFoundException (404 status) that wraps the thrown exception and no entity "

    Moral of the story: make sure to catch any possible exceptions in the ParamConverter!

See Also:

Upvotes: 8

Related Questions