web-dev-qa-db-fra.com

Comment utiliser LocalDateTime RequestParam au printemps? J'obtiens "Impossible de convertir String en LocalDateTime"

J'utilise Spring Boot et inclus jackson-datatype-jsr310 avec Maven:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.7.3</version>
</dependency>

Quand j'essaie d'utiliser un RequestParam avec un type Java 8 Date/Time),

@GetMapping("/test")
public Page<User> get(
    @RequestParam(value = "start", required = false)
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start) {
//...
}

et le tester avec cette URL:

/test?start=2016-10-8T00:00

Je reçois l'erreur suivante:

{
  "timestamp": 1477528408379,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.web.method.annotation.MethodArgumentTypeMismatchException",
  "message": "Failed to convert value of type [Java.lang.String] to required type [Java.time.LocalDateTime]; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [Java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam @org.springframework.format.annotation.DateTimeFormat Java.time.LocalDateTime] for value '2016-10-8T00:00'; nested exception is Java.lang.IllegalArgumentException: Parse attempt failed for value [2016-10-8T00:00]",
  "path": "/test"
}
33
Kery Hu

TL; DR - vous pouvez le capturer sous forme de chaîne avec seulement @RequestParam, Ou vous pouvez également faire analyser par Spring la chaîne dans un Java date/classe de temps via @DateTimeFormat sur le paramètre également.

le @RequestParam est suffisant pour saisir la date que vous fournissez après le signe =, cependant, il entre dans la méthode en tant que String. C'est pourquoi il jette l'exception de casting.

Il y a plusieurs façons d'y parvenir:

  1. analyser la date vous-même, en saisissant la valeur sous forme de chaîne.
@GetMapping("/test")
public Page<User> get(@RequestParam(value="start", required = false) String start){

    //Create a DateTimeFormatter with your required format:
    DateTimeFormatter dateTimeFormat = 
            new DateTimeFormatter(DateTimeFormatter.BASIC_ISO_DATE);

    //Next parse the date from the @RequestParam, specifying the TO type as 
a TemporalQuery:
   LocalDateTime date = dateTimeFormat.parse(start, LocalDateTime::from);

    //Do the rest of your code...
}
  1. Tirez parti de la capacité de Spring à analyser et à attendre automatiquement les formats de date:
@GetMapping("/test")
public void processDateTime(@RequestParam("start") 
                            @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) 
                            LocalDateTime date) {
        // The rest of your code (Spring already parsed the date).
}
19
Bwvolleyball

Vous avez tout fait correctement :). Here est un exemple qui montre exactement ce que vous faites. Juste Annotez votre RequestParam avec @DateTimeFormat. Il n’est pas nécessaire de procéder à une conversion spéciale GenericConversionService ou manuelle dans le contrôleur. Ceci blog écrit à ce sujet.

@RestController
@RequestMapping("/api/datetime/")
final class DateTimeController {

    @RequestMapping(value = "datetime", method = RequestMethod.POST)
    public void processDateTime(@RequestParam("datetime") 
                                @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateAndTime) {
        //Do stuff
    }
}

Je suppose que vous avez eu un problème avec le format. Sur ma configuration, tout fonctionne bien.

49
d0x

Comme je l'ai mis dans le commentaire, vous pouvez également utiliser cette solution dans la méthode de signature: @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start

13
Anna

J'ai trouvé une solution de contournement ici .

Spring/Spring Boot prend uniquement en charge le format de date/date-heure dans les paramètres BODY.

Cette classe de configuration ajoute la prise en charge de la date/date-heure dans QUERY STRING:

@Configuration
public class DateTimeFormatConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setUseIsoFormat(true);
        registrar.registerFormatters(registry);
    }
}

Cela fonctionne même si vous liez plusieurs paramètres de requête à une classe (@DateTimeFormat annotation impuissante dans ce cas):

public class ReportRequest {
    private LocalDate from;
    private LocalDate to;

    public LocalDate getFrom() {
        return from;
    }

    public void setFrom(LocalDate from) {
        this.from = from;
    }

    public LocalDate getTo() {
        return to;
    }

    public void setTo(LocalDate to) {
        this.to = to;
    }
}

// ...

@GetMapping("/api/report")
public void getReport(ReportRequest request) {
// ...
11
Lu55

J'ai rencontré le même problème et trouvé ma solution ici (sans utiliser les annotations)

... vous devez au moins enregistrer correctement une chaîne dans [LocalDateTime] Converter dans votre contexte, afin que Spring puisse l'utiliser pour le faire automatiquement à chaque fois que vous indiquez une chaîne en entrée et que vous attendez une [LocalDateTime]. (Un grand nombre de convertisseurs sont déjà implémentés par Spring et contenus dans le package core.convert.support, mais aucun n’implique une conversion [LocalDateTime])

Donc, dans votre cas, vous feriez ceci:

public class StringToLocalDateTimeConverter implements Converter<String, LocalDateTime> {
    public LocalDateTime convert(String source) {
        DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
        return LocalDateTime.parse(source, formatter);
    }
}

et ensuite, enregistrez simplement votre haricot:

<bean class="com.mycompany.mypackage.StringToLocalDateTimeConverter"/>

avec des annotations

ajoutez-le à votre service de conversion:

@Component
public class SomeAmazingConversionService extends GenericConversionService {

    public SomeAmazingConversionService() {
        addConverter(new StringToLocalDateTimeConverter());
    }

}

et enfin vous auriez alors @Autowire dans votre service de conversion:

@Autowired
private SomeAmazingConversionService someAmazingConversionService;

Vous pouvez en savoir plus sur les conversions avec spring (et le formatage) sur ce site . Soyez prévenu, il contient une tonne d’annonces, mais j’ai trouvé que c’était un site utile et une bonne introduction au sujet.

3
Naruto Sempai

Suivre fonctionne bien avec Spring Boot 2.1.6:

contrôleur

@Slf4j
@RestController
public class RequestController {

    @GetMapping
    public String test(RequestParameter param) {
        log.info("Called services with parameter: " + param);
        LocalDateTime dateTime = param.getCreated().plus(10, ChronoUnit.YEARS);
        LocalDate date = param.getCreatedDate().plus(10, ChronoUnit.YEARS);

        String result = "DATE_TIME: " + dateTime + "<br /> DATE: " + date;
        return result;
    }

    @PostMapping
    public LocalDate post(@RequestBody PostBody body) {
        log.info("Posted body: " + body);
        return body.getDate().plus(10, ChronoUnit.YEARS);
    }
}

classes Dto:

@Value
public class RequestParameter {
    @DateTimeFormat(iso = DATE_TIME)
    LocalDateTime created;

    @DateTimeFormat(iso = DATE)
    LocalDate createdDate;
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PostBody {
    LocalDate date;
}

Classe de test:

@RunWith(SpringRunner.class)
@WebMvcTest(RequestController.class)
public class RequestControllerTest {

    @Autowired MockMvc mvc;
    @Autowired ObjectMapper mapper;

    @Test
    public void testWsCall() throws Exception {
        String pDate        = "2019-05-01";
        String pDateTime    = pDate + "T23:10:01";
        String eDateTime = "2029-05-01T23:10:01"; 

        MvcResult result = mvc.perform(MockMvcRequestBuilders.get("")
            .param("created", pDateTime)
            .param("createdDate", pDate))
          .andExpect(status().isOk())
          .andReturn();

        String payload = result.getResponse().getContentAsString();
        assertThat(payload).contains(eDateTime);
    }

    @Test
    public void testMapper() throws Exception {
        String pDate        = "2019-05-01";
        String eDate        = "2029-05-01";
        String pDateTime    = pDate + "T23:10:01";
        String eDateTime    = eDate + "T23:10:01"; 

        MvcResult result = mvc.perform(MockMvcRequestBuilders.get("")
            .param("created", pDateTime)
            .param("createdDate", pDate)
        )
        .andExpect(status().isOk())
        .andReturn();

        String payload = result.getResponse().getContentAsString();
        assertThat(payload).contains(eDate).contains(eDateTime);
    }


    @Test
    public void testPost() throws Exception {
        LocalDate testDate = LocalDate.of(2015, Month.JANUARY, 1);

        PostBody body = PostBody.builder().date(testDate).build();
        String request = mapper.writeValueAsString(body);

        MvcResult result = mvc.perform(MockMvcRequestBuilders.post("")
            .content(request).contentType(APPLICATION_JSON_VALUE)
        )
        .andExpect(status().isOk())
        .andReturn();

        ObjectReader reader = mapper.reader().forType(LocalDate.class);
        LocalDate payload = reader.readValue(result.getResponse().getContentAsString());
        assertThat(payload).isEqualTo(testDate.plus(10, ChronoUnit.YEARS));
    }

}
1
Michael Hegner

Les réponses ci-dessus n'ont pas fonctionné pour moi, mais j'ai gaffé sur l'une d'entre elles: https://blog.codecentric.de/fr/2017/08/parsing-of-localdate-query-parameters- in-spring-boot / L'extrait gagnant était l'annotation ControllerAdvice, ce qui présente l'avantage d'appliquer ce correctif à tous vos contrôleurs:

@ControllerAdvice
public class LocalDateTimeControllerAdvice
{

    @InitBinder
    public void initBinder( WebDataBinder binder )
    {
        binder.registerCustomEditor( LocalDateTime.class, new PropertyEditorSupport()
        {
            @Override
            public void setAsText( String text ) throws IllegalArgumentException
            {
                LocalDateTime.parse( text, DateTimeFormatter.ISO_DATE_TIME );
            }
        } );
    }
}
1
user2659207