简体   繁体   中英

Why won't the date conversion work in Spring on requests with content-type json-patch?

I am getting a " Failed to convert from type [java.lang.String] to type [@javax.persistence.Column java.util.Date] for value '1999-12-20' " when I use json-patch, but I do not get this error with PUT, POST or event PATCH requests with json-merge-patch;

I am using Spring Boot version 2.1.8, with Spring Data Rest.

I have an entity with a field similar to the following (only the name of the variable and column are different):

@Column(name = "mydate")
private Date mydate;

The field is updated as expected when I issue a JSON merge patch request with the body:

{"mydate": "1999-12-20"}

It is also updated correctly with POST and PUT requests.

However, if I issue a json-patch with the following command (and Content-Type application/json-patch+json):

[{"op":"replace","path":"mydate","value":"2018-08-09"}]

I get the error mentioned above.

I don't actually need a workaround for this, as I am using the JSON Merge Patch for that.

I'd like to understand how to make the conversion that works for the other requests work for json-patch as well.

Here is a suggestion: Since Java 8 class Date should not be used, and it should be replaced with some implementation of Temporal interface. For example in your case LocalDate . And as far as Annotations for such property you'd want to annotate it as follows:

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
public LocalDate myDate;

For details refer to the answer to this question: Spring Data JPA - ZonedDateTime format for json serialization

This is a workaround, not a proper solution.

I still don't understand why this happens, but here are two workarounds that can be helpful for the next poor soul who stumbles in this issue:

Use JSON Merge Patch

Patch requests with JSON Merge Patch have their date fields properly interpreted by SDR. If possible, simply switching from using JSON Patch to JSON Merge Patch will solve the situation.

Use a dummy field

I couldn't find a way to help Spring Data Rest to use a Converter when it receives a JSON Patch request, so creating a dummy String field in your DTO (or worst ― in your Entity) will fix the issue.

If you can't or don't want to avoid using json-patch , this may be helpful.

This is not a beautiful solution and one might call you names upon seeing it, but it will work.

Consider the following ― worst case scenario ― code :

@Entity
public class MyRenderVouz {
    ...
    @Column(name="important_date")
    private LocalDate importantDate;
    
    @Transient
    private String strChangeImportantDate;
    public void setStrChangeImportantDate(String newDate) {
        setImportantDate(LocaleDate.parse(newDate);
    }
    public String getStrChangeImportantDate() {
        return this.importantDate;
    }
    ...
}

In your JSON-PATCH, simple use the strChangeImportantDate instead of importantDate, for example, consider you want to update the importantDate of the second instance of MyRendezVouz that belong to a calendar:

Calendar
 L importantRenderVouz  []
    L myRenderVouz
    L myRenderVouz
        L importantDate // target date
    L myRenderVouz

Then you can use:

[
    {"op":"replace","path":"importantRenderVouz/1/strImportantDate","value":"2020-01-01"},  
]

What I do when I have to perform patch request for an Entity with LocalDateTime attribute :

Lets for clarity say that your Entity having attribute LocalDateTime date is modeled with an Event.class:

@NoArgsConstructor
@Getter
@Setter
@Entity(name = "events")
...
public class Event {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long eventId;

    ...

    @Column(nullable = false)
    private LocalDateTime date;

    ...

1) Se externally the LocalDateTime format pattern in application.properties file:

config.datetime.format.pattern=yyyy-MM-dd HH:mm

2) Create a class AppConfig.class (or something like that) to extract the value from the application.properties file:

@Configuration
public class AppConfig {
    @Value("${config.datetime.format.pattern}")
    private String formatPattern;

    public DateTimeFormatter getDateTimeFormatter(){
        return DateTimeFormatter.ofPattern(formatPattern);
    }
}

3) Add the AppConfig dependency to your service class:

@Service
public class EventService {
    private final EventRepository eventRepo;
    private final EventDtoMapper eventDtoMapper;
    private final AppConfig appConfig;
    ...

    @Autowired
    public EventService(EventRepository eventRepo, EventDtoMapper eventDtoMapper, AppConfig appConfig, ...) {
        this.eventRepo = eventRepo;
        this.eventDtoMapper = eventDtoMapper;
        this.appConfig = appConfig;
        ...
    }

4) Write the service code for the function that will do the partial update:

 public EventDtoForCreate updatePartially(Long id, Map<String, String> fields) {
        Optional<Event> found = eventRepo.findById(id);

        if (found.isEmpty()) {
            // custom exception
            throw new ResourceNotFoundException("Failed to find event");
        }

        fields.forEach((key, value) -> {
            Field field = ReflectionUtils.findField(Event.class, key);
            if (field != null) {
                field.setAccessible(true);
                if (field.getAnnotatedType().getType().equals(LocalDateTime.class)) {
                    ReflectionUtils.setField(field, found.get(), LocalDateTime.parse(value, appConfig.getDateTimeFormatter()));
                } else {
                    ReflectionUtils.setField(field, found.get(), value);
                }
            }
        });

        return eventDtoMapper.toDtoCreate(eventRepo.save(found.get()));
    }

5) Finally, create the controller for the patch request:

    @PatchMapping(
            value = "/{id}",
            produces = {MediaType.APPLICATION_JSON_VALUE}
    )
    public ResponseEntity<EventDtoCreate> updatePartially(
            @PathVariable(name = "id") Long userId,
            @RequestBody Map<String, String> fields
    ) {
        return ResponseEntity.ok(eventService.updatePartially(userId, fields));
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM