This article is participating in the Java Theme Month – Java Debug Notes Event, see the event link for details

Author: Tangyuan

Personal blog: Javalover.cc

background

Sometimes when writing an interface, we need to convert the Date String from the foreground to Date

At this point we might use the @dateTimeFormat annotation

This annotation is fine and usable when requesting data in a non-JSON format;

However, problems arise when the requested data is in JSON format

  • If @requestBody is not added to the request parameter, no conversion will be performed on the request parameter, and the data will default to null (basic type such as int = 0, object reference such as Date Date = null).
  • If the request parameter is annotated with @requestBody, the request parameter will perform a JSON conversion, but the conversion will raise an exception

So what the title of the article says is sometimes invalid, refers to the above two cases

directory

This article is divided into three steps, as shown below, interspersed with @DateTimeFormat, @RequestBody, and @JsonFormat annotations

Analysis of the

1. Basic code:

Annationapplication.java: main program and controller

package com.jalon.annation;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class AnnationApplication {

	public static void main(String[] args) {
		SpringApplication.run(AnnationApplication.class, args);
	}

	@PostMapping("/personPost")
	public Person personPost(Person person){
		System.out.println(person);
		returnperson; }}Copy the code

Person. Java entity class

package com.jalon.annation;

import com.fasterxml.jackson.annotation.JacksonAnnotation;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;

import java.util.Date;

public class Person {

    private int age;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date birth;

    @Override
    public String toString(a) {
        return "Person{" +
                "age=" + age +
                ", birth=" + birth +
                '} ';
    }
		/ / omit getter/setter
}

Copy the code

2. Case Study:

Here we use PostMan to test, the request example is as follows

All examples are annotated @dateTimeFormat throughout

Example 1:

  • Request mode: Post request

  • Data format: Non-JSON format, such as form-data

  • Request resource: personPost(Person Person), without @requestBody annotation

    The specific request content and result are as follows

As you can see, the foreground returns normal, indicating that @datetimeFormat is valid and the date string was successfully parsed

The data returned here is processed by @responseBody, and since we have not configured date formatting for the returned data, the date format returned here is the default

@responseBody corresponds to @requestBody;

  • The former is responsible for returning the Java object serial number as JSON data
  • The latter is responsible for parsing the requested JSON data into the corresponding Java objects

Let’s take a look at the background and print the following:

Person{age=1, birth=Wed Jan 01 00:00:00 CST 2020}
Copy the code

As you can see, the background prints correctly (the data is correct, and the date format is ignored because date.tostring uses the default date method).

As you can see from the above results, @dateTimeFormat is only responsible for parsing the date string into the corresponding date object;

But it does not change the format of the original date object (as you can see from the foreground and background output, the date format is not affected by @dateTimeFormat).

Example 2:

  • Request mode: Post request

  • Data format: JSON format, such as Application/JSON

  • Request resource: personPost(Person Person), without @requestBody annotation

    The specific request content and result are as follows

As you can see, the return value is null (the default initial value), indicating that no data has been passed, not only date, but even the basic int

Let’s go back to the background, and print it like this

Person{age=0, birth=null} // Return the same data as the previous platform
Copy the code

As you can see, the data parsed in the background is also empty, so of course the data returned above is empty

The reason for this is that the default type converter does not have the corresponding conversion class to JSON format. Part of the converter is shown below (core-convert.support package).

So the solution here is to create your own JSON converter

But this is already implemented, just not triggered, and the build tool shown below (http.Converter. json package) is used to configure the associated JSON serialization and deserialization

We can now trigger this with the @RequestBody annotation, which automatically invokes the corresponding JSON converter when it receives jSON-formatted data

Example 3 below is an example of this

If you add @requestBody, you will accept only application/ JSON data by default. If you pass in other formats, 415 will not support the data type

Example 3:

  • Request mode: Post request

  • Data format: JSON format, such as Application/JSON

  • Request resource: personPost(@requestBody Person Person) with @requestBody annotation

    The specific request content and result are as follows

Error 400, which is usually a client error (e.g. data format is incorrect, data is too large, etc.)

Let’s go back to the background, and print it like this

2021-05-15 13:48:41.578  WARN 38426 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.Date` from String "The 2020-01-01 00:00:00": not a valid representation (error: Failed to parse Date value '2020-01-01 00:00:00': Cannot parse date "The 2020-01-01 00:00:00": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX', parsing fails (leniency? null)); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "The 2020-01-01 00:00:00": not a valid representation (error: Failed to parse Date value '2020-01-01 00:00:00': Cannot parse date "The 2020-01-01 00:00:00": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX', parsing fails (leniency? null))
 at [Source: (PushbackInputStream); line: 3, column: 14] (through reference chain: com.jalon.annation.Person["birth"]]Copy the code

Here we extract the key parts:

1. nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "The 2020-01-01 00:00:00"
 
2. Cannot parse date "The 2020-01-01 00:00:00": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX'
Copy the code

First, unlike example 2, the conversion was at least attempted, but the format was not found, so the conversion failed

As you can see, it is not parsed by our @datetimeFormat annotation above, but by ‘YYYY-MM-DD ‘T’HH: MM: ss.sssx’

‘YYYY-MM-DD ‘T’HH: MM :ss.SSSX” yyyY-MM-DD ‘T’HH: MM :ss.SSSX’ ‘yyyY-MM-DD ‘T’HH: MM :ss.

But this approach is very unfriendly to the front end (extremely bad)

So here’s the normal solution

** So the solution here is to define your own date format

  • Solution 1: local annotations, such as adding @jsonFormat () annotations to the date field
// This annotation parses the date string in the JSON data and serializes the returned data
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date birth;
Copy the code

Local features: flexible, but cumbersome configuration, not unified (each field must be added)

  • Scheme 2: global configuration to solve, such as configuring a Jackson2ObjectMapperBuilderCustomizer, then deserialize custom date format
package com.jalon.annation;

import com.fasterxml.jackson.databind.deser.std.DateDeserializers;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import java.text.SimpleDateFormat;
import java.util.Date;

@Configuration
public class MyDateConvertCustoms implements Jackson2ObjectMapperBuilderCustomizer {
    @Override
    public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
			// Override the default Date deserialization. The first parameter is the class to be deserialized and the second parameter is the specific serialization format
      jacksonObjectMapperBuilder.deserializerByType(
                Date.class
                ,new DateDeserializers.DateDeserializer(
                        DateDeserializers.DateDeserializer.instance
                        , new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"),null)); }}Copy the code

Global features: Inflexible, but clear, unified configuration

3. Conclusion Analysis:

The comparison is based primarily on the data type requested

  • To request non-JSON data, use @datetimeformat (for example, get requests can also request JSON data, but not recommended).
  • To request JSON data, it is recommended to convert the data with @reqeustbody, and then modify the default date parsing format (default “YYYY-MM-DD ‘T’HH: MM: ss.sssx “) with a local annotation @jsonFormat or global configuration.

conclusion

Notes related:

  1. @DateTimeFormatNote: This applies to requests for non-JSON data,The returned data is not formatted
  2. @JsonFormatNote: This applies to JSON data (especially date data) and @requestBody before the parameter of the request method.Returns formatted data
  3. @RequestBodyNote: Parse the incoming JSON data into the corresponding Java object
  4. @ResponseBodyNote: Convert Java objects into JSON data to be output to the front end as return data

Date formatting related:

  1. To request non-JSON data, use @datetimeformat. In this case, no data will be formatted. (Get requests can also request JSON data, but not recommended.)

  2. To request JSON data, it is recommended to convert the data with @reqeustbody, with a local annotation @jsonFormat (which will format the returned data) or global configuration to modify the default date parsing format (default “YYYY-MM-DD ‘T’HH: MM: ss.sssx “); The global configuration can also format returned data by configuring Builder.serializerByType

  3. If the date formatting fails, look at whether the data that is coming in is JSON data (you can use Consumes to limit it), and then look at whether there is global configuration for annotations or date formatting

Reference content:

  • @ RequestBody: blog.csdn.net/justry_deng…
  • @ DateTimeFormat: segmentfault.com/a/119000002…

Afterword.

The road to learning is long

Write at the end:

May the right person be the right person for you