【问题标题】:Ignore fields from Java object dynamically while sending as JSON from Spring MVC在从 Spring MVC 作为 JSON 发送时动态忽略 Java 对象中的字段
【发布时间】:2014-05-30 20:13:54
【问题描述】:

我有这样的模型类,用于休眠

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

View 部分是使用 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"
}

我不想发送给来自其他数据库表的客户端的字段列表。所以它会根据登录的用户而改变。我该怎么做?

我希望你有我的问题。

【问题讨论】:

标签: java json hibernate spring-mvc


【解决方案1】:

您好,我已经使用 Gson 库实现了动态过滤,如下所示:

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

【讨论】:

    【解决方案2】:

    要实现动态过滤,请点击链接 - 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。

    【讨论】:

      【解决方案3】:

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

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

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

      GitHub example

      【讨论】:

      • 我可以动态做吗?不在 POJO 中?我可以在我的 Controller 类中这样做吗?
      • @iProgrammer :这是你想要的类似:stackoverflow.com/questions/8179986/…
      • @iProgrammer:这里的答案非常令人印象深刻stackoverflow.com/questions/13764280/…
      • remark:@JsonIgnorecom.fasterxml.jackson.annotation.JsonIgnore 不是 org.codehaus.jackson.annotate.JsonIgnore
      • 在读取请求和发送响应时都会忽略。我只想在发送响应时忽略,因为我需要请求对象中的该属性。有什么想法吗?
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        我只使用@kryger 建议的@JsonIgnore 解决了问题。 所以你的吸气剂会变成:

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

        您当然可以在字段、setter 或 getter 上设置@JsonIgnore,如here 所述。

        而且,如果您只想在序列化方面保护加密密码(例如,当您需要登录用户时),请将此 @JsonProperty 注释添加到您的字段

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

        更多信息here

        【讨论】:

          【解决方案6】:

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

          @JsonIgnore
          private String encryptedPwd;
          

          或如上面提到的ceekay@JsonProperty 对其进行注释,其中访问属性设置为只写

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

          【讨论】:

            【解决方案7】:

            这是针对上述answer 的干净实用工具:

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

            【讨论】:

              【解决方案8】:

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

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

              【讨论】:

                【解决方案9】:

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

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

                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)。

                希望这会有所帮助。

                【讨论】:

                • @Shafqat Man,非常感谢你,你是我的救星。花了将近一天的时间试图找出这种功能。这个解决方案如此优雅和简单?并完全按照要求进行。应标记为正确答案。
                【解决方案10】:

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

                看起来像

                @Entity
                @JsonInclude(JsonInclude.Include.NON_NULL)
                

                【讨论】:

                • 完全不相关的回答。问题的目的不同,而答案是关于其他的。 -1
                【解决方案11】:

                我创建了一个 JsonUtil,可用于在运行时忽略字段,同时给出响应。

                示例用法: 第一个参数应该是任何 POJO 类(学生),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 "";
                   }
                 }    
                

                【讨论】:

                  【解决方案12】:

                  我可以动态做吗?

                  创建视图类:

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

                  【讨论】:

                  • 这是一个奇妙而简约的答案!我想以 JSON 形式返回 @Configuration 注释组件中的几个字段,并跳过所有自动包含的内部字段。非常感谢!
                  【解决方案13】:

                  创建UserJsonResponse 类并填充所需字段不是更清洁的解决方案吗?

                  当您想要返回所有模型时,直接返回 JSON 似乎是一个很好的解决方案。否则它只会变得一团糟。

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

                  【讨论】:

                    【解决方案14】:

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

                    最后我可以在需要时在 restConstoller 中指定一个特定的过滤器......

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

                    【讨论】:

                    • 一个简单的问题为什么这么复杂
                    【解决方案15】:

                    我知道我参加聚会有点晚了,但实际上几个月前我也遇到了这个问题。所有可用的解决方案对我来说都不是很吸引人(mixins?呃!),所以我最终创建了一个新库来使这个过程更干净。如果有人想尝试一下,可以在这里找到: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"));
                    

                    【讨论】:

                    • 谢谢!这就是我要走的路。我需要在不同位置具有相同对象的自定义 JSON 视图,而 @JsonIgnore 就是行不通。这个库让它变得非常简单。
                    • 你让我的代码更清晰,实现更容易。谢谢
                    • @monitorjbl :这有点偏离轨道,我使用了 json 视图并解决了我的目的。但是我无法为 java.util.Date 类注册自定义序列化程序(没有运行时/编译时错误),就像我能够注册自定义序列化程序的字符串一样。
                    • JsonView 现在似乎是 Jackson 对象映射器的一部分
                    【解决方案16】:

                    @JsonInclude(JsonInclude.Include.NON_NULL)(强制Jackson 序列化空值)添加到类以及@JsonIgnore 到密码字段。

                    如果您总是想忽略,当然也可以在 createdBy 和 updatedBy 上设置 @JsonIgnore,而不仅仅是在这种特定情况下。

                    更新

                    如果您不想将注释添加到 POJO 本身,一个不错的选择是 Jackson 的 Mixin Annotations。查看documentation

                    【讨论】:

                    • 我可以动态做吗?不在 POJO 中?我可以在我的 Controller 类中这样做吗?
                    • 你的意思是不想给POJO添加注解?
                    • 因为有时我可能想将所有字段发送到客户端。有时很少有字段。我应该发送到客户端的字段仅从控制器类中的数据库中获取。之后,我只需要设置应该忽略哪些字段。
                    猜你喜欢
                    • 2020-01-16
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-04-24
                    • 1970-01-01
                    相关资源
                    最近更新 更多