【问题标题】:Jackson - Properties Mapped Why Access.WRITE_ONLY Does not work?Jackson - 属性映射为什么 Access.WRITE_ONLY 不起作用?
【发布时间】:2022-01-03 16:29:57
【问题描述】:

为什么 Jackson 不生成“属性 ID”?

结果运行 Main

{id=1, name=Marcelo, email=test@gmail.com, password=123456, token=t1234, refre ...}
{id=1, name=Marcelo, email=test@gmail.com, password=null, enable=true}
propertie id > {"name":"Marcelo","email":"test@gmail.com","enable":true}

问题在于属性 id 映射为

@JsonProperty(access = Access.WRITE_ONLY) 当 Serializable 不产生属性和你的价值时

jackson 2.13.0,但其他版本存在bug

在github上有项目模拟器的问题 Bug Jackson Serializable propertie

在此处显示代码

主类

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 + "}";
    }
}

用户类

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 = "test@gmail.com";

    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 + "}";
    }
}

【问题讨论】:

  • 也许您应该澄清一下这些访问限制的原因。我认为使用这些存在一些误解。对我来说,代码的工作方式与文档中的一样。

标签: java serialization jackson


【解决方案1】:

reference documentation 对此非常清楚。在WRITE_ONLY,您可以阅读以下内容:

访问设置,表示该属性只能被写入(设置) 用于反序列化,但不会在序列化时读取(获取),即 也就是说,该属性的值不包含在序列化中。

这清楚地表明,在序列化(在您的情况下为 JSON 的 Java 对象)包含它们的对象时,将不会包含带有 @JsonProperty(access = Access.WRITE_ONLY) 注释的属性。

在我看来,您交换了访问类型。我想以下是你想要的:

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;

    (...)
}

鉴于您使用ObjectMapperUser 映射到UserDTO,那么唯一的选择是在id 中删除@JsonProperty,如下所示:

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;

    (...)
}

【讨论】:

  • 你在做测试吗?
  • 在结果的第二行 ok!我们的 id 和 value 正确。如果您的建议结果更改错误,则 id=null 并且密码包含有值。
  • 如何参数化属性以获得正确的结果(第 2 行是正确的结果)?
  • 嗯,这就是使用ObjectMapperUser 映射到UserDTO 的结果。您可能想使用mapstruct 来代替。不过,如果您只是从id 中删除@JsonProperty,它会按您的需要工作。
  • 是的,但是在系统的其他点接收时,不能考虑id,回答我,Access.WRITE_ONLY的目的是什么?使用 Serializable ,因为当我使用它时它不起作用?
【解决方案2】:

我建议您不要将 Jackson 库用于实体映射,因为它通过序列化和反序列化对象来实现。 WH 这很慢,可能会导致很多难以检测的错误。

我建议你使用像mapstruct 这样的映射库。

例如,以下将创建一个从 UserDto 映射到 User 的映射器,反之亦然。映射到 UserDto 时,它将忽略密码。将自动找到并映射所有名称相同的字段。

@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);
}

你的代码中的用法是这样的:

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();
        }
    }
}

jackson 库非常适合序列化 OR 反序列化,但此过程不如直接将一个实体映射到另一个实体那样快速和可靠。我认为 Jackson 文档的以下摘录已经暗示,不推荐使用 convert 函数进行实体到实体的转换:

进一步注意,在某些情况下,行为确实可能与完整的序列化-然后-反序列化循环不同:在大多数情况下,差异是无意的(即要修复的缺陷)并且应该报告,但不能保证行为100% 相同:目标是允许根据标准 Jackson 配置对结构兼容的对象进行有效的值转换。

最后,此功能并非旨在支持“高级”用例,例如多态值的转换或使用对象标识的情况。

来源:ObjectMapper#convertValue

【讨论】:

    猜你喜欢
    • 2016-12-14
    • 2023-03-25
    • 2016-02-10
    • 1970-01-01
    • 2018-09-15
    • 2011-12-23
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多