spring-projects / spring-projects/spring-data-rest

Serializing fails when @JsonView and @JsonFormat attributes are present [DATAREST-759]

Open
#1,132 1 comment 0 reactions 1 assignee View on GitHub

@odrotbohm is already working on this.

Since Dec 31, 2020.

in: repository type: bug
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 @Id field due to the same logic in PersistentEntityJackson2Module.updateBuilder.
  • Adding explicit @JsonSerializer for 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.