Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson - Properties Mapped Why Access.WRITE_ONLY Does not work?

Why Jackson not produce "propertie id"?

Result run Main

{id=1, name=Marcelo, [email protected], password=123456, token=t1234, refre ...}
{id=1, name=Marcelo, [email protected], password=null, enable=true}
propertie id > {"name":"Marcelo","email":"[email protected]","enable":true}

The problem consist in propertie id mapped with

@JsonProperty(access = Access.WRITE_ONLY) When Serializable not produces propertie and your value

jackson 2.13.0, but bug existin in others versions

In github has project simulator the problem Bug Jackson Serializable propertie

Show code here

Main.class

package com;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;

public class Main {

    private ObjectMapper model;

    public Main() {
        model = modelMapper();
    }

    public static void main(String[] args) {
        var main = new Main();
        var userEntity = new User();
        System.out.println(userEntity.toString());
        var user = main.getModel().convertValue(userEntity, UserDTO.class);
        System.out.println(user.toString());
        try {
            System.out.println(main.getModel().writeValueAsString(user));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    public ObjectMapper getModel() {
        return model;
    }

    private ObjectMapper modelMapper() {
        var model = new ObjectMapper();
        model.registerModule(new SimpleModule());
        model.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        model.setSerializationInclusion(JsonInclude.Include.NON_ABSENT);
        model.configure(SerializationFeature.USE_EQUALITY_FOR_OBJECT_ID, false);
        return model;
    }
}

UserDTO.class

package com;

import java.io.Serializable;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;


public class UserDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    @JsonProperty(access = Access.WRITE_ONLY)
    private Long id;

    private String name;

    private String email;

    @JsonProperty(access = Access.READ_ONLY)
    private String password;

    private Boolean enable = false;

    public Long getId() {
        return id;
    }


    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }


    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Boolean getEnable() {
        return enable;
    }

    public void setEnable(Boolean enable) {
        this.enable = enable;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }


    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        UserDTO other = (UserDTO) obj;
        return Objects.equals(id, other.id);
    }


    @Override
    public String toString() {
        return "{id=" + id + ", name=" + name + ", email=" + email + ", password=" + password + ", enable="
    + enable + "}";
    }
}

User.class

package com;

import java.io.Serializable;
import java.util.Date;
import java.util.Objects;

public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id = 1L;

    private String name = "Marcelo";

    private String email = "[email protected]";

    private String password = "123456";

    private String token = "t1234";

    private String refresh = "t2345";

    private Boolean enable = true;

    private Boolean hashAble = true;

    private Date dataLastChange = new Date();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getRefresh() {
        return refresh;
    }

    public void setRefresh(String refresh) {
        this.refresh = refresh;
    }

    public Boolean getEnable() {
        return enable;
    }

    public void setEnable(Boolean enable) {
        this.enable = enable;
    }

    public Boolean getHashAble() {
        return hashAble;
    }

    public void setHashAble(Boolean hashAble) {
        this.hashAble = hashAble;
    }

    public Date getDataLastChange() {
        return dataLastChange;
    }

    public void setDataLastChange(Date dataLastChange) {
        this.dataLastChange = dataLastChange;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        User other = (User) obj;
        return Objects.equals(id, other.id);
    }

    @Override
    public String toString() {
        return "{id=" + id + ", name=" + name + ", email=" + email + ", password=" + password + ", token=" + token
    + ", refresh=" + refresh + ", enable=" + enable + ", hashAble=" + hashAble + ", dataLastChange="
    + dataLastChange + "}";
    }
}
like image 683
Marcelo Ferreira Avatar asked Nov 25 '21 11:11

Marcelo Ferreira


People also ask

What is JsonProperty access Write_only?

public static final JsonProperty.Access WRITE_ONLY. Access setting that means that the property may only be written (set) for deserialization, but will not be read (get) on serialization, that is, the value of the property is not included in serialization.

What is JsonProperty access?

public static final JsonProperty.Access READ_WRITE. Access setting that means that the property will be accessed for both serialization (writing out values as external representation) and deserialization (reading values from external representation), regardless of visibility rules.

What does @json property do?

@JsonProperty is used to mark non-standard getter/setter method to be used with respect to json property.

What is Jackson JsonProperty?

The @JsonProperty annotation is used to map property names with JSON keys during serialization and deserialization. By default, if you try to serialize a POJO, the generated JSON will have keys mapped to the fields of the POJO.


2 Answers

The reference documentation is pretty clear about this. In WRITE_ONLY you can read the following:

Access setting that means that the property may only be written (set) for deserialization, but will not be read (get) on serialization, that is, the value of the property is not included in serialization.

This clearly states that properties annotated with @JsonProperty(access = Access.WRITE_ONLY) will not be included while serializing (Java object to JSON in your case) the object that contains them.

It seems to me that you swapped the access type. I guess that the following is what you want:

public class UserDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    @JsonProperty(access = Access.READ_ONLY)
    private Long id;

    private String name;

    private String email;

    @JsonProperty(access = Access.WRITE_ONLY)
    private String password;

    private Boolean enable = false;

    (...)
}

Given that you are using ObjectMapper to map from User to UserDTO, then the only option is to get rid of @JsonProperty in id as follows:

public class UserDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    private String name;

    private String email;

    @JsonProperty(access = Access.WRITE_ONLY)
    private String password;

    private Boolean enable = false;

    (...)
}
like image 197
João Dias Avatar answered Nov 14 '22 22:11

João Dias


I suggest you not to use the Jackson library for entity mapping as it does it by serializing and deserializing an object. WHich is slow and can lead to a lot of errors that are hard to detect.

I recommend that you use a mapping library like mapstruct.

For example, the following will create a mapper that mapps from UserDto to User and vice versa. It will ignore the password when mapping to UserDto. All fields that are named the same will automatically be found and mapped.

@Mapper
public abstract class UserMapper {
    public static final UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mapping(target = "password", ignore = true)
    public abstract UserDto toUserDto(User user);


    public abstract User toUser(UserDto userDto);
}

usage would be like this in your code:

public class Main {
    public static void main(String[] args) {
        var user= new User();
        System.out.println(userEntity.toString());
        var userDto = UserMapper.INSTANCE.toUserDto(userEntity);
        System.out.println(userDto.toString());
        try {
            System.out.println((new ObjectMapper()).writeValueAsString(userDto));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
}

The jackson library is great for serializing OR deserializing, but this process is not as fast and reliable as direct mapping one entity into another one. I think the following excerpt from the Jackson documentation already hints at that it is not the recommended way to use the convert function for entity to entity conversion:

Further note that it is possible that in some cases behavior does differ from full serialize-then-deserialize cycle: in most case differences are unintentional (that is, flaws to fix) and should be reported, but the behavior is not guaranteed to be 100% the same: the goal is to allow efficient value conversions for structurally compatible Objects, according to standard Jackson configuration.

Finally, this functionality is not designed to support "advanced" use cases, such as conversion of polymorphic values, or cases where Object Identity is used.

source: ObjectMapper#convertValue

like image 24
3Fish Avatar answered Nov 14 '22 23:11

3Fish