当从 SpringMVC 以 JSON 格式发送时,动态忽略 Java 对象中的字段

我有这样的模型班,用来冬眠的

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

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

但是我不想这样,我不想发送所有字段到客户端。如果我不想密码,updatedby,createdby 字段发送,我的结果 JSON 应该像

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

我不想从其他数据库表发送到客户端的字段列表。因此,它将根据登录的用户进行更改。我该怎么做?

我希望你明白我的问题。

354908 次浏览

@JsonInclude(JsonInclude.Include.NON_NULL)(强制 Jackson 序列化空值)添加到类中,将 @JsonIgnore添加到 password 字段中。

当然,如果您总是希望忽略,也可以将 @JsonIgnore设置为 createdBy 和 updatedBy,而不仅仅是在这个特定的情况下。

更新

如果您不想将注释添加到 POJO 本身,一个很好的选择是 Jackson 的 Mixin 注释。看看 文件

@JsonIgnoreProperties("fieldname")注释添加到 POJO。

或者你可以在反序列化 JSON 的时候在你想忽略的字段名之前使用 @JsonIgnore:

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

GitHub 的例子

我知道我来得有点晚,但我几个月前也遇到过这种情况。所有可用的解决方案对我来说都不是很有吸引力?啊!)所以我最终创建了一个新的库来使这个过程更加清晰。如果有人想试试的话,可以在这里下载: 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"));

如果我是你,并希望这样做,我不会使用我的用户实体在控制器层。相反,我创建并使用 UserDto (数据传输对象)与业务(服务)层和控制器进行通信。 可以使用 ApacheBeanUtils (copProperties 方法)将数据从 User 实体复制到 UserDto。

我在 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);
}


}

弹簧配置:

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


}


}

最后,我可以指定一个特定的过滤器在重新控制器时,我需要... 。

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


}

在实体类中添加 @JsonInclude(JsonInclude.Include.NON_NULL)注释以解决问题

看起来就像

@Entity
@JsonInclude(JsonInclude.Include.NON_NULL)

创建一个 UserJsonResponse类并填充想要的字段不是一个更干净的解决方案吗?

当您希望返回所有模型时,直接返回 JSON 似乎是一个很好的解决方案。否则就麻烦了。

例如,在将来,您可能希望拥有一个与任何 Model 字段都不匹配的 JSON 字段,那么您将面临更大的麻烦。

我能动态地做吗?

创建视图类:

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

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

我已经创建了一个 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 "";
}
}

是的,您可以指定哪些字段被序列化为 JSON 响应,以及哪些字段要忽略。 这就是实现动态忽略属性所需要做的。

1)首先,您需要在您的实体类中添加来自 com.fasterxml.jackson.annotion. 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"
}

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

希望这个能帮上忙。

我们可以通过在声明属性时设置对 JsonProperty.Access.WRITE_ONLY的访问来实现这一点。

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

这是一个干净的实用工具,以上 回答:

@GetMapping(value = "/my-url")
public @ResponseBody
MappingJacksonValue getMyBean() {
List<MyBean> myBeans = Service.findAll();
MappingJacksonValue mappingValue = MappingFilterUtils.applyFilter(myBeans, MappingFilterUtils.JsonFilterMode.EXCLUDE_FIELD_MODE, "MyFilterName", "myBiggerObject.mySmallerObject.mySmallestObject");
return mappingValue;
}


//AND THE UTILITY CLASS
public class MappingFilterUtils {


public enum JsonFilterMode {
INCLUDE_FIELD_MODE, EXCLUDE_FIELD_MODE
}
public static MappingJacksonValue applyFilter(Object object, final JsonFilterMode mode, final String filterName, final String... fields) {
if (fields == null || fields.length == 0) {
throw new IllegalArgumentException("You should pass at least one field");
}
return applyFilter(object, mode, filterName, new HashSet<>(Arrays.asList(fields)));
}


public static MappingJacksonValue applyFilter(Object object, final JsonFilterMode mode, final String filterName, final Set<String> fields) {
if (fields == null || fields.isEmpty()) {
throw new IllegalArgumentException("You should pass at least one field");
}


SimpleBeanPropertyFilter filter = null;
switch (mode) {
case EXCLUDE_FIELD_MODE:
filter = SimpleBeanPropertyFilter.serializeAllExcept(fields);
break;
case INCLUDE_FIELD_MODE:
filter = SimpleBeanPropertyFilter.filterOutAllExcept(fields);
break;
}


FilterProvider filters = new SimpleFilterProvider().addFilter(filterName, filter);
MappingJacksonValue mapping = new MappingJacksonValue(object);
mapping.setFilters(filters);
return mapping;
}
}

像@kryger 建议的那样,我只用 @JsonIgnore解决了这个问题。 因此,你得到的会变成:

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

当然,您可以像描述的 给你那样在字段、 setter 或 getter 上设置 @JsonIgnore

而且,如果你只想在序列化端保护加密的密码(例如,当你需要登录你的用户时) ,将这个 @JsonProperty注释添加到你的 场地:

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

更多信息 给你

@JsonIgnore放在字段或其 getter 上,或创建一个自定义 dto

@JsonIgnore
private String encryptedPwd;

或者像上面提到的 ceekay@JsonProperty注释它,其中访问属性设置为只写

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

实现动态过滤跟踪链接 -https://iamvickyav.medium.com/spring-boot-dynamically-ignore-fields-while-converting-java-object-to-json-e8d642088f55

  1. 将@JsonFilter (“ Filter name”)注释添加到模型类。

  2. 在控制器函数中添加代码:-

    SimpleBeanPropertyFilter simpleBeanPropertyFilter =
    SimpleBeanPropertyFilter.serializeAllExcept("id", "dob");
    
    
    FilterProvider filterProvider = new SimpleFilterProvider()
    .addFilter("Filter name", simpleBeanPropertyFilter);
    
    
    List<User> userList = userService.getAllUsers();
    MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(userList);
    mappingJacksonValue.setFilters(filterProvider);
    
    
    return mappingJacksonValue;
    
  3. 确保返回类型为 MappingJacksonValue。

嗨,我已经通过使用 葛森库实现了动态过滤,如下所示:

JsonObject jsonObj = new Gson().fromJson(mapper.writeValueAsString(sampleObject), JsonObject.class);
jsonObj.remove("someProperty");
String data = new Gson().toJson(jsonObj);

我能动态地做吗?

是的,你可以结合使用杰克逊的 PropertyFilterMixins

解释

Jackson 有一个 PropertyFilter接口来实现动态忽略字段的过滤器。问题是必须使用 @JsonFilter注释在 DTO/POJO 类上定义过滤器。

为了避免在类上添加 @JsonFilter,我们可以使用 ObjectMapperaddMixIn方法来为 “动态”添加这个注释(并保持我们的 DTO/POJO 类的原样)。

代码示例

下面是我对上面提供的想法的实现。我们可以用两个参数调用 toJson(): (1)要序列化的对象和(2)在 PropertyFilter 中使用的 lambda (Java 的谓词) :

public class JsonService {


public String toJson(Object object, Predicate<PropertyWriter> filter) {
ObjectMapper mapper = new ObjectMapper();
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("DynamicFilter", new DynamicFilter(filter));
mapper.setFilterProvider(filterProvider);
mapper.addMixIn(object.getClass(), DynamicFilterMixin.class);
try {
return mapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new MyException(e);
}
}


private static final class DynamicFilter extends SimpleBeanPropertyFilter {


private Predicate<PropertyWriter> filter;


private DynamicFilter(Predicate<PropertyWriter> filter) {
this.filter = filter;
}


protected boolean include(BeanPropertyWriter writer) {
return include((PropertyWriter) writer);
}


protected boolean include(PropertyWriter writer) {
return filter.test(writer);
}
}


@JsonFilter("DynamicFilter")
private interface DynamicFilterMixin {
}


}

现在我们可以在序列化过程中调用 toJson和过滤字段:

  1. 按名字过滤
new JsonService().toJson(object, w -> !w.getName().equals("fieldNameToBeIgnored"));
  1. 通过注释(在字段上)进行过滤
new JsonService().toJson(object, w -> w.getAnnotation(MyAnnotation.class) == null);

单元测试

下面是上面这个类的单元测试:

public class JsonServiceTest {
    

private JsonService jsonService = new JsonService();
    

@Test
public void withoutFiltering() {
MyObject object = getObject();
String json = jsonService.toJson(object, w -> true);
assertEquals("{\"myString\":\"stringValue\",\"myInteger\":10,\"myBoolean\":true}", json);
}


@Test
public void filteredByFieldName() {
MyObject object = getObject();
String json = jsonService.toJson(object, w -> !w.getName().equals("myString"));
assertEquals("{\"myInteger\":10,\"myBoolean\":true}", json);
}


@Test
public void filteredByAnnotation() {
MyObject object = getObject();
String json = jsonService.toJson(object, w -> w.getAnnotation(Deprecated.class) == null);
assertEquals("{\"myString\":\"stringValue\",\"myInteger\":10}", json);
}
    

private MyObject getObject() {
MyObject object = new MyObject();
object.myString = "stringValue";
object.myInteger = 10;
object.myBoolean = true;
return object;
}
    

private static class MyObject {
private String myString;
private int myInteger;
@Deprecated
private boolean myBoolean;


public String getMyString() {
return myString;
}


public void setMyString(String myString) {
this.myString = myString;
}


public int getMyInteger() {
return myInteger;
}


public void setMyInteger(int myInteger) {
this.myInteger = myInteger;
}


public boolean isMyBoolean() {
return myBoolean;
}


public void setMyBoolean(boolean myBoolean) {
this.myBoolean = myBoolean;
}
}


}