Spring REST API register date converter for pojo

Sergii :

Spring rest provides building pojo functionality by default from path variables and url parameters.

In my case I have pojo:

public class MyCriteria {
  private String from;
  private String till;
  private Long communityNumber;
  private String communityName;
}

that is used in my controller. Url is http://localhost:8080/community/{communityNumber}/app. Request result of

curl "http://localhost:8080/community/1/app?from=2018-11-14&till=2019-05-13&communityName=myCOm"

is:

{
  'from':'2018-11-14';
  'till':'2019-05-12';
  'communityNumber':'1';
  'communityName':'myCOm'
}

It seems works fine. Much more better to have in pojo data with required types by purposes. So I'd like to have fields from and till of LocalDate type. Using spring I'd like to have this solution almost out the box. But any spring or jackson date converters can't resolve my issue because of life cycle.

Spring validates types of pojo fields before injecting dates and I'm get type mismatch exception. I think the general reason is using special builder by spring that tries to find required parameters by name and it ignores annotations to be applied inside of pojo for the fields.

Question is:

Is there any elegant solutions for building pojo by spring where some fields will be converted from String to LocalDate format by default?

P.S.

Mandatory conditions are:

  • request method is GET;
  • required pojo is:
public class MyCriteria {
  private LocalDate from;
  private LocalDate till;
  private Long communityNumber;
  private String communityName;
}
  • lets skip ideas with custom AOP implementations or with getters where injected convert logic;
  • there are neither body (request body is empty) nor json (all required data are path variables or path params).

P.S.2.

  • Controller example that can be used for experiments is:
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import org.vl.example.rest.dtofromoaramsandpath.web.dto.MyCriteria;

import java.time.LocalDate;

@RestController
@RequestMapping("verify/criteria/mapping")
@Slf4j
public class MyController {

    @GetMapping("community/{communityNumber}/dto")
    public MyCriteria loadDataByDto(MyCriteria criteria) {
        log.info("received criteria: {}", criteria);
        return criteria;
    }

    @GetMapping("community/{communityNumber}/default/params")
    public String loadDataByDefaultParameters(@PathVariable("communityNumber") String communityNumber,
                                            @RequestParam(value = "from", required = false) String from,
                                            @RequestParam(value = "till", required = false) String till,
                                            @RequestParam(value = "communityName", required = false) String communityName) {
        log.info("received data without converting:\n\tcommunityNumber => {}\n\tfrom => {}\n\ttill => {}\n\tcommunityName => {}",
                communityNumber, from, till, communityName);
        return new StringBuilder("{")
                .append("\n\tfrom:").append(from).append(";")
                .append("\n\tfrom:").append(from).append(";")
                .append("\n\ttill:").append(till).append(";")
                .append("\n\tcommunityName:").append(communityName)
                .append("\n}\n").toString();
    }

    @GetMapping("community/{communityNumber}/converted/params")
    public String loadUsingConvertedParameters(@PathVariable("communityNumber") String communityNumber,
                                             @RequestParam(value = "from") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
                                             @RequestParam("till") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate till,
                                             @RequestParam(value = "communityName", required = false) String communityName) {
        log.info("received data with LocalDate converting:\n\tcommunityNumber => {}\n\tfrom => {}\n\ttill => {}\n\tcommunityName => {}",
                communityNumber, from, till, communityName);
        return new StringBuilder("{")
                .append("\n\tfrom:").append(from).append(";")
                .append("\n\tfrom:").append(from).append(";")
                .append("\n\ttill:").append(till).append(";")
                .append("\n\tcommunityName:").append(communityName)
                .append("\n}\n").toString();
    }
}
  • also link on github project that contains module with required controller and criteria to make your understanding and experiments more useful.
Sergii :

The answer on question is to use init binder to register type mapping inside of specified criteria. It is required to have PropertyEditorSupport implementation for specified purpose.

Short code example is:

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(LocalDate.parse(text, DateTimeFormatter.ISO_LOCAL_DATE));
            }
        });
    }

Full code example can be taken from github:

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.vl.example.rest.dtofromoaramsandpath.web.dto.MyCriteriaLd;

import java.beans.PropertyEditorSupport;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

@RestController
@RequestMapping("verify/criteria/mapping")
@Slf4j
public class MyControllerLd {
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(LocalDate.parse(text, DateTimeFormatter.ISO_LOCAL_DATE));
            }
        });
    }

    @GetMapping("community/{communityNumber}/dtold")
    public MyCriteriaLd loadDataByDto(MyCriteriaLd criteria) {
        log.info("received criteria: {}", criteria);
        return criteria;
    }
}

So model for this case can be next:

import lombok.Data;

import java.time.LocalDate;

@Data
public class MyCriteriaLd {
    private LocalDate from;
    private LocalDate till;
    private Long communityNumber;
    private String communityName;
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=323523&siteId=1