spring-projects / spring-projects/spring-data-rest
Serializing fails when @JsonView and @JsonFormat attributes are present [DATAREST-759]
@odrotbohm is already working on this.
Since Dec 31, 2020.
- Dominant language
- Java
- Stars
- 958
- Forks
- 568
- PR merge metrics
- No merged PRs in 30d
Description
Florin Duroiu opened DATAREST-759 and commented
There's a wicked issue I'm facing: I have the following domain class backed by a RDBMS table (postgresql). Using jackson-databind 2.6.4 and spring-data-rest-webmvc 2.4.2.RELEASE
@Getter @Setter
@Entity(name="ACCOUNT")
public class User extends ResourceSupport {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//more properties here
@ManyToOne(optional = false)
@JoinColumn(name = "account_state")
@JsonView(UserView.AdminVisible.class)
private AccountState accountState;
@LastModifiedDate
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'", timezone = "UTC")
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Date updatedAt;
@CreatedDate
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'", timezone = "UTC")
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
Date createdAt;
}
AccountState is yet another entity that is backed by a separate table.
I have a controller that has @JsonView(UserView.UserVisible.class) annotated methods where the accountState field is not exposed. I use spring.jackson.mapper.default-view-inclusion=true
to avoid annotating each field with @JsonView(...UserVisible.class).
I am also exposing a UserRepository extends PagingAndSortingRepository<User, Long> repo (for admin access). Here there is no @JsonView filtering applied and normally all attributes should be exposed.
The problem I'm facing is that when I create (or retrieve an existing) entity via the UserRepository CRUD interface, I get the following exception:
Could not write content: java.sql.Timestamp cannot be cast to java.lang.String
After investigating the cause, I found the found the following:
PersistentEntityJackson2Module.updateBuilder filters-out the accountState since it is isLinkableAssociation.
This causes BeanSerializerBase to have different _props and _filteredProps arrays, with _filteredProps being one element less than _props
In turn, this causes the following code in BeanSerializerBase.resolve to set the wrong serializer to the wrong property since the _props and _filteredProps arrays don't match:
// and maybe replace filtered property too? (see [JACKSON-364])
if (i < filteredCount) {
BeanPropertyWriter w2 = _filteredProps[i];
if (w2 != null) {
w2.assignSerializer(ser);
}
}
To fix this behaviour one has to literally relocate the accountState attribute (the one that resolves to linkable association) at the bottom of the domain class. This way, even if the _props and _filteredProps don't match, the first n-1 elements will match and thus the serialization will succeed.
Some more info:
- The same behaviour happens when having an
@Idfield due to the same logic in PersistentEntityJackson2Module.updateBuilder. - Adding explicit
@JsonSerializerfor attributes is also a workaround this issue due to "if(prop.hasSerializer()) {continue;} " bit in BeanSerializerBase.resolve that skips the problematic code above altogether.
Any help in the matter is appreciated. In the mean time I'm applying the fix above
Affects: 2.4.2 (Gosling SR2)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.