在Spring MVC中将Java对象作为JSON发送时动态忽略字段

150

我有这样一个模型类,用于Hibernate:

@Entity
@Table(name = "user", catalog = "userdb")
@JsonIgnoreProperties(ignoreUnknown = true)
public class User implements java.io.Serializable {

    private Integer userId;
    private String userName;
    private String emailId;
    private String encryptedPwd;
    private String createdBy;
    private String updatedBy;

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "UserId", unique = true, nullable = false)
    public Integer getUserId() {
        return this.userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    @Column(name = "UserName", length = 100)
    public String getUserName() {
        return this.userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Column(name = "EmailId", nullable = false, length = 45)
    public String getEmailId() {
        return this.emailId;
    }

    public void setEmailId(String emailId) {
        this.emailId = emailId;
    }

    @Column(name = "EncryptedPwd", length = 100)
    public String getEncryptedPwd() {
        return this.encryptedPwd;
    }

    public void setEncryptedPwd(String encryptedPwd) {
        this.encryptedPwd = encryptedPwd;
    }

    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }

    @Column(name = "UpdatedBy", length = 100)
    public String getUpdatedBy() {
        return this.updatedBy;
    }

    public void setUpdatedBy(String updatedBy) {
        this.updatedBy = updatedBy;
    }
}
在Spring MVC的控制器中,使用DAO获取对象,并将其作为JSON对象返回。
@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "/getUser/{userId}", method = RequestMethod.GET)
    @ResponseBody
    public User getUser(@PathVariable Integer userId) throws Exception {

        User user = userService.get(userId);
        user.setCreatedBy(null);
        user.setUpdatedBy(null);
        return user;
    }
}

视图部分使用AngularJS完成,因此它将会获取如下的JSON数据

{
  "userId" :2,
  "userName" : "john",
  "emailId" : "john@gmail.com",
  "encryptedPwd" : "Co7Fwd1fXYk=",
  "createdBy" : null,
  "updatedBy" : null
}

如果我不想设置加密密码,我会将该字段设为空。

但我不希望这样做,我不想将所有字段都发送到客户端。如果我不想发送密码、更新者、创建者字段,那么我的结果 JSON 应该是这样的:

{
  "userId" :2,
  "userName" : "john",
  "emailId" : "john@gmail.com"
}

我不想把来自其他数据库表的某些字段发送给客户端,因此它会根据已登录的用户而改变。我该怎么做?

希望您理解我的问题。


关于这个答案,你怎么看?https://dev59.com/v10a5IYBdhLWcg3wPWhN#30559076 - Iryna
这些信息可能会有所帮助。 https://dev59.com/bGcs5IYBdhLWcg3w433H#36965995 - Musa
我在这里回答了动态过滤字段的简单方法 - Edward Abattam
在JSON响应中动态忽略字段,可以在发送响应之前将该字段设置为null,例如entityClass.setFieldToIgnore(null)。Spring-MVC默认设置为在反序列化时忽略null值,或者您可以手动配置它。 - Andrew Mititi
18个回答

189

在你的POJO中添加@JsonIgnoreProperties("fieldname")注解。

或者你可以在要忽略JSON反序列化的字段名称前使用@JsonIgnore。例如:

@JsonIgnore
@JsonProperty(value = "user_password")
public String getUserPassword() {
    return userPassword;
}

GitHub示例


96
我能翻译。您的意思是:我可以动态地做吗?不使用POJO?我可以在我的控制器类中做吗? - iCode
3
@iProgrammer:这里有一个与您想要的类似的链接:https://dev59.com/yGsy5IYBdhLWcg3w-zF5 - user3145373 ツ
3
@iProgrammer 在这里给出了非常令人印象深刻的答案:https://dev59.com/9WYr5IYBdhLWcg3wVYsg。 - user3145373 ツ
15
@JsonIgnore 表示的是 com.fasterxml.jackson.annotation.JsonIgnore 而非 org.codehaus.jackson.annotate.JsonIgnore - xiaohuo
9
这个忽略的问题同时涉及到读取请求和发送响应。我只想在发送响应时忽略它,因为我需要请求对象中的该属性。有任何想法吗? - zulkarnain shah
显示剩余14条评论

51

我能动态地做吗?

创建视图类:

public class View {
    static class Public { }
    static class ExtendedPublic extends Public { }
    static class Internal extends ExtendedPublic { }
}

为您的模型添加注释

@Document
public class User {

    @Id
    @JsonView(View.Public.class)
    private String id;

    @JsonView(View.Internal.class)
    private String email;

    @JsonView(View.Public.class)
    private String name;

    @JsonView(View.Public.class)
    private Instant createdAt = Instant.now();
    // getters/setters
}

在您的控制器中指定视图类

@RequestMapping("/user/{email}")
public class UserController {

    private final UserRepository userRepository;

    @Autowired
    UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @RequestMapping(method = RequestMethod.GET)
    @JsonView(View.Internal.class)
    public @ResponseBody Optional<User> get(@PathVariable String email) {
        return userRepository.findByEmail(email);
    }

}

数据示例:

{"id":"5aa2496df863482dc4da2067","name":"test","createdAt":"2018-03-10T09:35:31.050353800Z"}

更新:请记住,在响应中使用实体并不是最佳实践。最好为每种情况使用不同的DTO,并使用modelmapper填充它们。


4
这是一份精彩而简洁的答案!我想从一个@Configuration注解的组件中仅返回几个字段,跳过所有自动包含的内部字段,并将其作为JSON返回。非常感谢! - stx
1
这应该是被接受的解决方案。真的很有帮助。谢谢@Hett。 - Daman Arora
不要忘记添加spring.jackson.mapper.default-view-inclusion=true以级联到子对象,请参见此处https://stackoverflow.com/a/57928379/18209257 - nomadus

41

我知道我来晚了,但几个月前我也遇到过这个问题。所有可用的解决方案对我来说都不是很吸引人(混入?呃!),所以我最终创建了一个新库,以使这个过程更加清洁。如果有人想试试,它在这里可用:https://github.com/monitorjbl/spring-json-view

基本用法非常简单,在控制器方法中使用JsonView对象即可:

import com.monitorjbl.json.JsonView;
import static com.monitorjbl.json.Match.match;

@RequestMapping(method = RequestMethod.GET, value = "/myObject")
@ResponseBody
public void getMyObjects() {
    //get a list of the objects
    List<MyObject> list = myObjectService.list();

    //exclude expensive field
    JsonView.with(list).onClass(MyObject.class, match().exclude("contains"));
}

你也可以在Spring之外使用它:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import static com.monitorjbl.json.Match.match;

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(JsonView.class, new JsonViewSerializer());
mapper.registerModule(module);

mapper.writeValueAsString(JsonView.with(list)
      .onClass(MyObject.class, match()
        .exclude("contains"))
      .onClass(MySmallObject.class, match()
        .exclude("id"));

6
谢谢!对我来说,这是正确的方法。我需要自定义JSON视图,在不同位置使用相同的对象,但@JsonIgnore不起作用。这个库使得实现变得非常简单。 - Jeff
2
你让我的代码更加简洁,实现更加容易。谢谢! - anindis
@monitorjbl:有点偏题了,我已经使用了JSON视图并解决了我的问题。但是我无法为java.util.Date类注册自定义序列化程序(没有运行时/编译时错误),而对于字符串,我能够注册自定义序列化程序。 - Ninad
JsonView 现在似乎是 Jackson 对象映射器的一部分了。 - maxeh
@monitorjbl 很有趣,我想我会尝试你的库。 - Adel Ben Hamadi

23

我们可以通过在声明属性时设置JsonProperty.Access.WRITE_ONLY来实现此操作。

@JsonProperty( value = "password", access = JsonProperty.Access.WRITE_ONLY)
@SerializedName("password")
private String password;

以防万一有人来查找,我使用了这个方法来忽略请求中发送的属性,但仍然在响应中保留。其他解决方案,如@JsonIgnore会同时忽略请求和响应中的属性。 - kamasuPaul

22

是的,您可以指定哪些字段作为JSON响应进行序列化,哪些字段忽略。

要实现动态忽略属性,请按以下步骤操作:

1)首先,您需要在实体类上添加来自com.fasterxml.jackson.annotation.JsonFilter的@JsonFilter注释。

import com.fasterxml.jackson.annotation.JsonFilter;

@JsonFilter("SomeBeanFilter")
public class SomeBean {

  private String field1;

  private String field2;

  private String field3;

  // getters/setters
}

2) 然后在您的控制器中,您需要创建MappingJacksonValue对象并对其设置过滤器,最后返回此对象。

import java.util.Arrays;
import java.util.List;

import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;

@RestController
public class FilteringController {

  // Here i want to ignore all properties except field1,field2.
  @GetMapping("/ignoreProperties")
  public MappingJacksonValue retrieveSomeBean() {
    SomeBean someBean = new SomeBean("value1", "value2", "value3");

    SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field2");

    FilterProvider filters = new SimpleFilterProvider().addFilter("SomeBeanFilter", filter);

    MappingJacksonValue mapping = new MappingJacksonValue(someBean);

    mapping.setFilters(filters);

    return mapping;
  }
}

以下是您将收到的响应:

{
  field1:"value1",
  field2:"value2"
}

改为这样:

{
  field1:"value1",
  field2:"value2",
  field3:"value3"
}

这里您可以看到除了属性field1和field2之外,响应中忽略了其他属性(在本例中是field3)。

希望这可以帮助你。


1
@Shafqat Man,非常感谢你,你是我的救星。花了将近一天的时间试图找到这种功能。这个解决方案非常优雅和简单,并且恰好做到了要求。应该标记为正确答案。 - Oleg Kuts

16
@JsonInclude(JsonInclude.Include.NON_NULL)添加到类中,强制Jackson序列化null值,同时将@JsonIgnore添加到密码字段。如果您希望始终忽略createdBy和updatedBy,而不仅仅是在此特定情况下,请在它们上面设置@JsonIgnore更新:如果您不想将注释添加到POJO本身,则可以使用Jackson的Mixin Annotations。请查看文档

我可以动态地做吗?不用POJO对象?我可以在我的Controller类中做吗? - iCode
你的意思是你不想将注释添加到POJO中吗? - geoand
1
因为有时我可能想将所有字段发送到客户端,有时只想发送几个字段。应该发送到客户端的字段是在控制器类中从数据库获取的。之后,我需要设置应该忽略哪些字段。 - iCode

10

我已经按照@kryger建议的方法仅使用@JsonIgnore解决了问题。 所以你的getter将变为:

@JsonIgnore
public String getEncryptedPwd() {
    return this.encryptedPwd;
}

你可以像这个链接这里描述的那样,在字段、setter或getter上设置@JsonIgnore

如果你想要仅在序列化方面保护加密密码(例如当你需要登录你的用户时),请向你的字段添加@JsonProperty注释:

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

更多信息在这里


6
如果我是你,并且想要这样做,我不会在控制器层使用我的User实体。相反,我会创建并使用UserDto(数据传输对象)与业务(服务)层和控制器进行通信。您可以使用Apache BeanUtils(copyProperties方法)从User实体复制数据到UserDto中。

5

我创建了一个JsonUtil,可以在运行时忽略字段并生成响应。

使用示例: 第一个参数应该是任何POJO类(例如Student),ignoreFields是你想要在响应中忽略的逗号分隔字段。

 Student st = new Student();
 createJsonIgnoreFields(st,"firstname,age");

import java.util.logging.Logger;

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
import org.codehaus.jackson.map.ser.FilterProvider;
import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;

public class JsonUtil {

  public static String createJsonIgnoreFields(Object object, String ignoreFields) {
     try {
         ObjectMapper mapper = new ObjectMapper();
         mapper.getSerializationConfig().addMixInAnnotations(Object.class, JsonPropertyFilterMixIn.class);
         String[] ignoreFieldsArray = ignoreFields.split(",");
         FilterProvider filters = new SimpleFilterProvider()
             .addFilter("filter properties by field names",
                 SimpleBeanPropertyFilter.serializeAllExcept(ignoreFieldsArray));
         ObjectWriter writer = mapper.writer().withFilters(filters);
         return writer.writeValueAsString(object);
     } catch (Exception e) {
         //handle exception here
     }
     return "";
   }

   public static String createJson(Object object) {
        try {
         ObjectMapper mapper = new ObjectMapper();
         ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
         return writer.writeValueAsString(object);
        }catch (Exception e) {
         //handle exception here
        }
        return "";
   }
 }    

1
I've找到了一个使用Spring和jackson的解决方案。
首先,在实体中指定过滤器名称。
@Entity
@Table(name = "SECTEUR")
@JsonFilter(ModelJsonFilters.SECTEUR_FILTER)
public class Secteur implements Serializable {

/** Serial UID */
private static final long serialVersionUID = 5697181222899184767L;

/**
 * Unique ID
 */
@Id
@JsonView(View.SecteurWithoutChildrens.class)
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@JsonView(View.SecteurWithoutChildrens.class)
@Column(name = "code", nullable = false, length = 35)
private String code;

/**
 * Identifiant du secteur parent
 */
@JsonView(View.SecteurWithoutChildrens.class)
@Column(name = "id_parent")
private Long idParent;

@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "id_parent")
private List<Secteur> secteursEnfants = new ArrayList<>(0);

}

然后您可以在Spring配置中使用默认的FilterProvider,查看常量过滤器名称类。
public class ModelJsonFilters {

public final static String SECTEUR_FILTER = "SecteurFilter";
public final static String APPLICATION_FILTER = "ApplicationFilter";
public final static String SERVICE_FILTER = "ServiceFilter";
public final static String UTILISATEUR_FILTER = "UtilisateurFilter";

public static SimpleFilterProvider getDefaultFilters() {
    SimpleBeanPropertyFilter theFilter = SimpleBeanPropertyFilter.serializeAll();
    return new SimpleFilterProvider().setDefaultFilter(theFilter);
}

}

Spring配置:

@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "fr.sodebo")

public class ApiRootConfiguration extends WebMvcConfigurerAdapter {

@Autowired
private EntityManagerFactory entityManagerFactory;


/**
 * config qui permet d'éviter les "Lazy loading Error" au moment de la
 * conversion json par jackson pour les retours des services REST<br>
 * on permet à jackson d'acceder à sessionFactory pour charger ce dont il a
 * besoin
 */
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

    super.configureMessageConverters(converters);
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    ObjectMapper mapper = new ObjectMapper();

    // config d'hibernate pour la conversion json
    mapper.registerModule(getConfiguredHibernateModule());//

    // inscrit les filtres json
    subscribeFiltersInMapper(mapper);

    // config du comportement de json views
    mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);

    converter.setObjectMapper(mapper);
    converters.add(converter);
}

/**
 * config d'hibernate pour la conversion json
 * 
 * @return Hibernate5Module
 */
private Hibernate5Module getConfiguredHibernateModule() {
    SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
    Hibernate5Module module = new Hibernate5Module(sessionFactory);
    module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, true);

    return module;

}

/**
 * inscrit les filtres json
 * 
 * @param mapper
 */
private void subscribeFiltersInMapper(ObjectMapper mapper) {

    mapper.setFilterProvider(ModelJsonFilters.getDefaultFilters());

}

}

最后,当我需要时,我可以在restController中指定特定的过滤器....
@RequestMapping(value = "/{id}/droits/", method = RequestMethod.GET)
public MappingJacksonValue getListDroits(@PathVariable long id) {

    LOGGER.debug("Get all droits of user with id {}", id);

    List<Droit> droits = utilisateurService.findDroitsDeUtilisateur(id);

    MappingJacksonValue value;

    UtilisateurWithSecteurs utilisateurWithSecteurs = droitsUtilisateur.fillLists(droits).get(id);

    value = new MappingJacksonValue(utilisateurWithSecteurs);

    FilterProvider filters = ModelJsonFilters.getDefaultFilters().addFilter(ModelJsonFilters.SECTEUR_FILTER, SimpleBeanPropertyFilter.serializeAllExcept("secteursEnfants")).addFilter(ModelJsonFilters.APPLICATION_FILTER,
            SimpleBeanPropertyFilter.serializeAllExcept("services"));

    value.setFilters(filters);
    return value;

}

5
为什么一个简单的问题变得如此复杂? - Humoyun Ahmad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接