javajacksonjackson-databindimmutables-library

Selectively @JsonIgnore Immutables accessor methods, ONLY during serialization or deserialization using Jackson


This is definitely not a duplicate of Only using @JsonIgnore during serialization, but not deserialization. The problem is the same but in the context of Immutables.

When a model(DTO/DAO) is decorated as an Immutable, I am not able to selectively @JsonIgnore one of the properties during serialization. Suppose that we have a UserDto which is defined as an Immutable as follow

@Value.Immutable
@Value.Style(defaults = @Value.Immutable(copy = false), init = "set*")
@JsonSerialize(as = ImmutableUserDto.class)
@JsonDeserialize(builder = ImmutableUserDto.Builder.class)
public abstract class UserDto {

    @JsonProperty("id")
    @Value.Default
    public int getId() {
        return 0;
    }

    @JsonProperty("username")
    public abstract String getUsername();

    @JsonProperty("email")
    public abstract String getEmail();

    @JsonProperty("password")
    public abstract String getPassword();
}

I believe it is fair to expect that during serialization we would want to ignore the password from the response of the service.

Without using Immutables if we were working with a simple class, then there are many ways to accomplish this. For example - annotate only the getter with @JsonIgnore. Or if possible define a different accessor method (something that doesn't have the get prefix) and only define the regular setter method... and so on.

If I try the same on the Immutables accessor method for the password as shown below:

@Value.Immutable
@Value.Style(defaults = @Value.Immutable(copy = false), init = "set*")
@JsonSersonIgnoreialize(as = ImmutableUserDto.class)
@JsonDeserialize(builder = ImmutableUserDto.Builder.class)
public abstract class UserDto {

    ....

    @JsonProperty("password")
    @JsonIgnore
    public abstract String getPassword();
}

then, the generated ImmutableUserDto adds the @JsonIgnore on both the getter and setter as shown below.

@Generated(from = "UserDto", generator = "Immutables")
@SuppressWarnings({"all"})
@ParametersAreNonnullByDefault
@javax.annotation.Generated("org.immutables.processor.ProxyProcessor")
@Immutable
@CheckReturnValue
public final class ImmutableUserDto extends UserDto {
  ...
  ...
  private final String password;

  ...
  ...

  /**
   * @return The value of the {@code password} attribute
   */
  @JsonProperty("password")
  @JsonIgnore
  @Override
  public String getPassword() {
    return password;
  }

  ...
  ...
  ...

  @Generated(from = "UserDto", generator = "Immutables")
  @NotThreadSafe
  public static final class Builder {

    ...
    ...

    private String password;

    @JsonProperty("password")
    @JsonIgnore
    public final Builder setPassword(String password) {
      this.password = password;
      return this;
    }
  }
}

Serialization will work as expected. The password attribute will be excluded from the JSON. But when I try to de-serialize, I get the following error:

java.lang.IllegalStateException: Cannot build UserDto, some of the required attributes are not set [password]

Which is obvious as Immutables added the @JsonIgnore to the setter as well.

The documentation isn't of much help. In their Things to be aware of section, it just mentions the following regarding @JsonIgnore

If using @JsonIgnore, you should explicitly make an attribute non-mandatory. In Immutables, an attribute can be declared as non-mandatory via @Nullable, Optional or @Value.Default which are all different in their effect and we do not derive anything automatically.

Using @Nullable or Optional or @Value.Default is not of any use in case of fields like password.

I have gone through the issue list on their GitHub page and there is a similar issue but the user was asking for a slightly different use case and using @Nullable could solve the problem which doesn't work in my case.

I have also tried to use one of the answers here. Still resulted in the same error.

It looked like this is not supported by Immutables library. I have created a new issue myself. Once I get some feedback from users on SOF, I will probably create a sscce.


Solution

  • I had to use the suggestion given by @benarena in this comment. However I had to explicitly specify the value attribute of the property along with the Access attribute.

    @JsonProperty(value = "password", access = JsonProperty.Access.WRITE_ONLY) solved the problem.

    The Immutable class would look like:

    @Value.Immutable
    @Value.Style(defaults = @Value.Immutable(copy = false), init = "set*")
    @JsonSersonIgnoreialize(as = ImmutableUserDto.class)
    @JsonDeserialize(builder = ImmutableUserDto.Builder.class)
    public abstract class UserDto {
    
        ....
    
        @JsonProperty(value = "password", access = JsonProperty.Access.WRITE_ONLY)
        public abstract String getPassword();
    }