I have patching problem which is related to converting the String value the corresponding type. When I try to patch the "Locale" type (or primitives), it works. But it fails for Instant
Entity:
@JsonIgnore
@Field("locale")
private Locale locale;
@JsonIgnore
@Field("dateOfBirth")
private Instant dateOfBirth;
@JsonIgnore
public Locale getLocale() {
return this.locale;
}
@JsonIgnore
public void setLocale(Locale locale) {
this.locale = locale;
}
@JsonIgnore
public Instant getDateOfBirth() {
return this.dateOfBirth;
}
@JsonIgnore
public void setDateOfBirth(Instant dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
Patch method:
public static <T> T applyPatchOnObject(Class<T> type, T object, JsonNode jsonNode) {
try {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
return new JsonPatchPatchConverter(mapper).convert(jsonNode).apply(object, type);
} catch (Exception e) {
throw new UnprocessableEntityException(e.getMessage());
}
}
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath />
</parent>
<!-- Date -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Data:
[{"op": "replace", "path": "dateOfBirth", "value": "1971-01-01T01:01:01.001Z"}]
The exception:
EL1034E: A problem occurred whilst attempting to set the property 'dateOfBirth': Type conversion failure
Deeper exception:
EL1001E: Type conversion problem, cannot convert from java.lang.String to @com.fasterxml.jackson.annotation.JsonIgnore @org.springframework.data.mongodb.core.mapping.Field java.time.Instant
Edit 1:
The following code blocks work:
Code: System.out.println(mapper.readValue("1517846620.12312312", Instant.class));
Result: 2018-02-05T16:03:40.123123120Z
The following code blocks DO NOT work:
Patch: [{"op": "replace", "path": "dateOfBirth", "value": "1517846620.12312312"}]
Solution:
Although the answer from @Babl will probably work, I figure the following things out.
As @Babl pointed out, the Spring framework patching is NOT done FasterXML but by Spring Expression Context so all Jackson annotations DO NOT take any effect.
I was patching the
User
entity directly which is VERY BAD practice.
So I ended up with the following implementation
The Patch library
<dependency>
<groupId>com.flipkart.zjsonpatch</groupId>
<artifactId>zjsonpatch</artifactId>
<version>${zjsonpatch.version}</version>
</dependency>
The Patch metod
public static <T extends EmbeddedResource> T applyPatchOnObject(Class<T> type, T object, JsonNode jsonNode) {
Assert.notNull(type, "Given type must not be null!");
Assert.notNull(object, "Given object must not be null!");
Assert.notNull(jsonNode, "Given jsonNode must not be null!");
try {
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
return mapper.convertValue(JsonPatch.fromJson(jsonNode).apply(mapper.convertValue(object, JsonNode.class)),
type);
} catch (Exception e) {
throw new UnprocessableEntityException(e.getMessage());
}
}
!NOTE: theapplyPatchOnObject
method ONLY accepts classes which extend EmbeddedResource
, which in extends ResourceSupport
. So basically DTOs only.
The entity is the same
Introduce UserDTO with all the proper Jackson
annotations:
@NotNull(message = "locale cannot be null")
@JsonProperty("locale")
private Locale locale;
@NotNull(message = "dateOfBirth cannot be null")
@JsonProperty("dateOfBirth")
private Instant dateOfBirth;
@JsonIgnore
public Locale getLocale() {
return this.locale;
}
@JsonIgnore
public void setLocale(Locale locale) {
this.locale = locale;
}
@JsonIgnore
public Instant getDateOfBirth() {
return this.dateOfBirth;
}
@JsonIgnore
public void setDateOfBirth(Instant dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
After I have my DTO patched with values. I will use ObjectMapper or some custom way to apply changes from the DTO to the Entity.
All recommendations and advices are welcome.
Basically, the problem is the fact that the data binding is not done by FasterXML but by Spring Expression Context. So adding the
jackson-datatype-jsr310
will not help at all. The FasterXML will be only used if the patchvalue
is object or an array. But in your case, the patchvalue
is string type soJsonPatchPatchConverter
will try to convert values using purely Spring tools (Spring Expression Context). So what now you are missing is the String to Instant converter for a Spring Framework. I'm quite sure that there are some implementations available and even maybe some are within the Spring libraries, but I will create here a simple one and show how you can register it. Initially, let's create a converter (Not the best implementation, just for proof of concept).And register it before calling the
applyPatchOnObject
methodSomething like this will work.