Ignore campos do objeto Java dinamicamente ao enviar como JSON do Spring MVC

105

Eu tenho uma classe modelo como esta, para hibernar

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

No controlador Spring MVC, usando DAO, sou capaz de obter o objeto. e retornando como objeto 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;
    }
}

A parte da visualização é feita usando AngularJS, então ele obterá JSON assim

{
  "userId" :2,
  "userName" : "john",
  "emailId" : "[email protected]",
  "encryptedPwd" : "Co7Fwd1fXYk=",
  "createdBy" : null,
  "updatedBy" : null
}

Se eu não quiser definir a senha criptografada, irei definir esse campo também como nulo.

Mas não quero assim, não quero mandar todos os campos para o lado do cliente. Se eu não quiser senha, atualizada por, criada por campos para enviar, Meu resultado JSON deve ser como

{
  "userId" :2,
  "userName" : "john",
  "emailId" : "[email protected]"
}

A lista de campos que não desejo enviar ao cliente proveniente de outra tabela do banco de dados. Portanto, ele mudará com base no usuário que está conectado. Como posso fazer isso?

Espero que você tenha entendido minha pergunta.

iCode
fonte
O que você diria sobre esta resposta? stackoverflow.com/a/30559076/3488143
Iryna
esta informação pode ser útil stackoverflow.com/questions/12505141/…
Musa

Respostas:

142

Adicione a @JsonIgnoreProperties("fieldname")anotação ao seu POJO.

Ou você pode usar @JsonIgnoreantes do nome do campo que deseja ignorar ao desserializar JSON. Exemplo:

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

Exemplo GitHub

user3145373 ツ
fonte
63
Posso fazer isso dinamicamente? Não está no POJO? Posso fazer isso na minha aula de controlador?
iCode de
3
@iProgrammer: aqui está um semelhante ao que você deseja: stackoverflow.com/questions/8179986/…
user3145373 ツ
3
@iProgrammer: uma resposta muito impressionante aqui stackoverflow.com/questions/13764280/…
user3145373 ツ
11
observação: não @JsonIgnoreé . com.fasterxml.jackson.annotation.JsonIgnoreorg.codehaus.jackson.annotate.JsonIgnore
xiaohuo
5
Isso ignora ao ler a solicitação e ao enviar a resposta. Quero ignorar apenas durante o envio de resposta, porque preciso dessa propriedade do objeto de solicitação. Alguma ideia?
zulkarnain shah
32

Sei que estou um pouco atrasado para a festa, mas na verdade também me deparei com isso alguns meses atrás. Todas as soluções disponíveis não eram muito atraentes para mim (mixins? Ugh!), Então acabei criando uma nova biblioteca para tornar esse processo mais limpo. Está disponível aqui se alguém quiser experimentá-lo: https://github.com/monitorjbl/spring-json-view .

O uso básico é muito simples, você usa o JsonViewobjeto em seus métodos de controlador assim:

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

Você também pode usá-lo fora do 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"));
monitorjbl
fonte
5
Obrigado! Este foi o caminho a percorrer para mim. Eu precisava de visualizações JSON personalizadas com os mesmos objetos em locais diferentes e @JsonIgnore simplesmente não funcionava. Essa biblioteca simplificou a tarefa.
Jeff
2
Você tornou meu código mais limpo e a implementação mais fácil. obrigado
anindis
@monitorjbl: isso está um pouco fora do caminho, usei visualizações json e está resolvendo meu propósito. Mas não consigo registrar o serializador personalizado para a classe java.util.Date (sem erro de tempo de execução / compilação), pois para string consegui registrar o serializador personalizado.
Ninad
17

Posso fazer isso dinamicamente?

Criar classe de visualização:

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

Faça anotações em seu modelo

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

Especifique a classe de visualização em seu controlador

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

}

Exemplo de dados:

{"id":"5aa2496df863482dc4da2067","name":"test","createdAt":"2018-03-10T09:35:31.050353800Z"}
Hett
fonte
1
Esta é uma resposta fantástica e minimalista! Eu queria retornar como um JSON apenas alguns campos de um componente anotado @Configuration e pular todos os campos internos que são incluídos automaticamente. Muito obrigado!
stx
15

Podemos fazer isso configurando o acesso para JsonProperty.Access.WRITE_ONLYao declarar a propriedade.

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

Adicione @JsonInclude(JsonInclude.Include.NON_NULL)(força Jackson a serializar valores nulos) para a classe, bem como @JsonIgnorepara o campo de senha.

É claro que você também pode definir @JsonIgnorecreatedBy e updatedBy se quiser sempre ignorá-los e não apenas neste caso específico.

ATUALIZAR

Caso você não queira adicionar a anotação ao próprio POJO, uma ótima opção são as Anotações do Mixin de Jackson. Confira a documentação

geoand
fonte
Posso fazer isso dinamicamente? Não está no POJO? Posso fazer isso na minha aula de controlador?
iCode de
Você quer dizer que não deseja adicionar as anotações ao POJO?
geoand
Porque às vezes posso querer enviar todos os campos para o lado do cliente. Às vezes, poucos campos. Os campos que devo enviar ao lado do cliente estão obtendo do banco de dados apenas na classe do controlador. Depois disso, só preciso definir quais campos devem ser ignorados.
iCode de
12

Sim, você pode especificar quais campos são serializados como resposta JSON e quais ignorar. Isso é o que você precisa fazer para implementar Propriedades de ignorar dinamicamente.

1) Primeiro, você precisa adicionar @JsonFilter de com.fasterxml.jackson.annotation.JsonFilter em sua classe de entidade como.

import com.fasterxml.jackson.annotation.JsonFilter;

@JsonFilter("SomeBeanFilter")
public class SomeBean {

  private String field1;

  private String field2;

  private String field3;

  // getters/setters
}

2) Em seguida, em seu controlador, você deve adicionar, criar o objeto MappingJacksonValue e definir filtros nele e no final, você deve retornar este objeto.

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

Esta é a resposta que você receberá:

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

em vez disso:

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

Aqui você pode ver que ele ignora outras propriedades (campo3 neste caso) em resposta, exceto para a propriedade campo1 e campo2.

Espero que isto ajude.

Shafqat Shafi
fonte
1
@Shafqat Man, muito obrigado, você é meu salvador. Passei quase um dia tentando descobrir esse tipo de funcionalidade. Esta solução é tão elegante e simples? e faz exatamente o que foi solicitado. Deve ser marcada como a resposta certa.
Oleg Kuts
6

Se eu fosse você e quisesse fazer isso, não usaria minha entidade de usuário na camada de controlador. Em vez disso, crio e uso UserDto (objeto de transferência de dados) para me comunicar com a camada de negócios (serviço) e o controlador. Você pode usar o Apache BeanUtils (método copyProperties) para copiar dados da entidade User para UserDto.

Hamedz
fonte
3

Eu criei um JsonUtil que pode ser usado para ignorar campos em tempo de execução ao fornecer uma resposta.

Exemplo de uso: O primeiro argumento deve ser qualquer classe POJO (Student) e ignoreFields são campos separados por vírgula que você deseja ignorar na resposta.

 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 "";
   }
 }    
Devendra Dora
fonte
2

@JsonIgnoreResolvi usar apenas como sugeriu @kryger. Portanto, seu getter se tornará:

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

Você pode definir o @JsonIgnorecurso no campo, setter ou getter conforme descrito aqui .

E, se você deseja proteger a senha criptografada apenas no lado da serialização (por exemplo, quando você precisa fazer o login de seus usuários), adicione esta @JsonPropertyanotação ao seu campo :

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

Mais informações aqui .

foxbit
fonte
1

Eu encontrei uma solução para mim com Spring e Jackson

Primeiro especifique o nome do filtro na entidade

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

}

Então você pode ver a classe de nomes de filtros de constantes com o FilterProvider padrão usado na configuração de primavera

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

}

Configuração da mola:

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

}

}

Por fim, posso especificar um filtro específico em restConstoller quando preciso ...

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

}
C2dric
fonte
5
por que complicações para uma pergunta fácil
Humoyun Ahmad
1

Coloque @JsonIgnoreno campo ou seu getter, ou crie um dto personalizado

@JsonIgnore
private String encryptedPwd;

ou como mencionado acima, ceekayanote-o com @JsonPropertyonde o atributo de acesso está definido para escrever apenas

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

Criar uma UserJsonResponseclasse e preencher com os campos desejados não seria uma solução mais limpa?

Retornar diretamente um JSON parece uma ótima solução quando você deseja devolver todo o modelo. Caso contrário, fica confuso.

No futuro, por exemplo, você pode querer ter um campo JSON que não corresponda a nenhum campo de modelo e então você está em um problema maior.

Leonardo Beal
fonte
0

Esta é uma ferramenta de utilidade limpa para a resposta acima :

@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;
    }
}
Mehdi
fonte
-5

Em sua classe de entidade, adicione @JsonInclude(JsonInclude.Include.NON_NULL)anotações para resolver o problema

vai parecer

@Entity
@JsonInclude(JsonInclude.Include.NON_NULL)
Jeevan Gowda
fonte
Totalmente irrelevante respondido. O propósito da pergunta é diferente, enquanto a resposta é sobre outra coisa. -1 para isso
Hammad Dar