0
votes

I have a Spring Rest Controller and a "command"/DTO object for the POST method on the controller. I also wrote a serializer / deserializer for one of the fields "due" - which is a Calendar object.

Since Jackson2 dependencies are defined in pom.xml, I expect Spring to detect my deserializer and use it to convert a String input to java.util.Calendar. However, I get a "no matching editors or conversion strategy found" exception. My serializer is working ... only the deserializer won't work!

Rest Controller (TaskController.java)

@Controller
public class TasksController {

    ...

    @RequestMapping(value = "/tasks", method = RequestMethod.POST)
    public @ResponseBody Task createTask(@Valid TasksCommand tasksCommand){

        Task task = new Task();
        task.setName(tasksCommand.getName());
        task.setDue(tasksCommand.getDue());
        task.setCategory(tasksCommand.getCategory());

        return task;
    }
}

The command / dto object:

public class TasksCommand {

    @NotBlank
    @NotNull
    private String name;

    @JsonDeserialize(using = CalendarDeserializer.class)
    @JsonSerialize(using = CalendarSerializer.class)
    private Calendar due;

    private String category;

    ... getters & setters ...
}

Serializer for Calendar - for my custom date format

public class CalendarSerializer extends JsonSerializer<Calendar>{

        @Override
        public void serialize(Calendar calendar, JsonGenerator jgen, SerializerProvider provider)
                throws IOException, JsonProcessingException {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss Z"); // Z - RFC 822 time zone

            jgen.writeString(simpleDateFormat.format(calendar.getTime()));

        }

}

Deserializer for Calendar

public class CalendarDeserializer extends JsonDeserializer<Calendar> {

    private static Logger logger = Logger.getLogger(CalendarDeserializer.class);

    @Override
    public Calendar deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
            throws IOException, JsonProcessingException {

        String dateStr = jsonParser.getText();
        logger.info("deserializing date:" + dateStr);

        return Calendar.getInstance();

    }
}

I have Jackson2 dependencies specified in Maven. When doing a GET (code not shown here), the serializer gets invoked correctly, and I see a JSON output of the "Task" object.

However, when I make an HTTP POST as below

curl -X POST -d name=task1 -d category=weekly -d due=01/01/2013 http://localhost:8080/tasks

the deserializer is never detected and I get an exception

Failed to convert property value of type java.lang.String to required type java.util.Calendar for property due; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [java.util.Calendar] for property due: no matching editors or conversion strategy found

Since the serializer is getting detected, I don't understand why the deserializer is not detected and invoked correctly. From what I have read, having Jackson2 library on classpath will be detected by Spring and it would automatically add MappingJackson2HttpMessageConverter to the list of message converters. So this code should detect the string in HTTP POST and convert it to java.util.Calendar using the Deserializer. What am I missing?

2

2 Answers

2
votes

Figured this one out.

When making an HTTP POST using Curl as shown in the description (with the -d flag), the POST comes in with content type "application/x-www-form-urlencoded" (obvious in hindsight, since it's treated as a web form data). This triggers the Spring FormHttpMessageConverter message converter and MappingJackson2HttpMessageConverter converter is never triggered.

However, the serializer and deserializer are annotated with Jackson annotations and only MappingJackson2HttpMessageConverter knows how to handle that. This is what causes the exception described in the problem.

The solution is to pass in JSON data in the curl command:

curl -X POST -H "Content-Type: application/json" -d '{"name":"test1","due":"1/1/2013","category":"weekly"}' http://localhost:8080/tasks

Also, since the JSON input comes in the body of the HTTP POST call, we need to use the @RequestBody annotation in the Controller method signature.

@RequestMapping(value = "/tasks", method = RequestMethod.POST)
    public @ResponseBody Task createTask(@RequestBody @Valid TasksCommand tasksCommand){

        Task task = new Task();
        task.setName(tasksCommand.getName());
        task.setDue(tasksCommand.getDue());
        task.setCategory(tasksCommand.getCategory());

        return task;
    }

Now the deserializer gets triggered and the input due date is converted from String to Calendar object correctly.

0
votes

try a custom class

public class DateDeserializer implements JsonDeserializer<Date> {

    @Override
    public Date deserialize(JsonElement element, Type arg1, JsonDeserializationContext arg2) throws JsonParseException {
        String date = element.getAsString();
        return TimeService.parseDate(date,"yyyy-MM-dd'T'HH:mm:ss");

    }
}

and then add it to your gson builder

GsonBuilder gsonBuilder = new GsonBuilder();
            gsonBuilder.registerTypeAdapter(Date.class, new DateDeserializer());
gsonBuilder.create().fromJson(eventsJson, yourTargetClass.class)