Ignore los campos del objeto Java de forma dinámica mientras envía como JSON desde Spring MVC

105

Tengo una clase 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;
    }
}

En el controlador Spring MVC, usando DAO, puedo obtener el objeto. y regresando 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;
    }
}

La parte de vista se realiza usando AngularJS, por lo que obtendrá JSON como este

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

Si no quiero establecer una contraseña cifrada, estableceré ese campo también como nulo.

Pero no quiero así, no quiero enviar todos los campos al lado del cliente. Si no quiero contraseña, campos actualizados por, creados por para enviar, mi resultado JSON debería ser

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

La lista de campos que no quiero enviar al cliente procede de otra tabla de base de datos. Entonces cambiará según el usuario que haya iniciado sesión. ¿Cómo puedo hacer eso?

Espero que tengas mi pregunta.

iCode
fuente
¿Qué dirías sobre esta respuesta? stackoverflow.com/a/30559076/3488143
Iryna
esta información puede ser útil stackoverflow.com/questions/12505141/…
Musa

Respuestas:

142

Agregue la @JsonIgnoreProperties("fieldname")anotación a su POJO.

O puede usar @JsonIgnoreantes del nombre del campo que desea ignorar mientras deserializa JSON. Ejemplo:

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

Ejemplo de GitHub

usuario3145373 ツ
fuente
63
¿Puedo hacerlo de forma dinámica? ¿No estás en POJO? ¿Puedo hacerlo en mi clase de Controlador?
iCode
3
@iProgrammer: aquí hay un similar al que desea: stackoverflow.com/questions/8179986/…
user3145373 ツ
3
@iProgrammer: respuesta muy impresionante aquí stackoverflow.com/questions/13764280/…
user3145373 ツ
11
observación: no lo @JsonIgnorees . com.fasterxml.jackson.annotation.JsonIgnoreorg.codehaus.jackson.annotate.JsonIgnore
Xiaohuo
5
Eso ignora tanto al leer la solicitud como al enviar la respuesta. Quiero ignorar solo al enviar la respuesta porque necesito esa propiedad del objeto de solicitud. ¿Algunas ideas?
zulkarnain shah
32

Sé que llego un poco tarde a la fiesta, pero en realidad me encontré con esto hace unos meses. Todas las soluciones disponibles no eran muy atractivas para mí (¿mixins? ¡Uf!), Así que terminé creando una nueva biblioteca para hacer este proceso más limpio. Está disponible aquí si alguien quisiera probarlo: https://github.com/monitorjbl/spring-json-view .

El uso básico es bastante simple, usa el JsonViewobjeto en los métodos de su controlador así:

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

También puede usarlo fuera de 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
fuente
5
¡Gracias! Este era el camino a seguir para mí. Necesitaba vistas JSON personalizadas con los mismos objetos en diferentes ubicaciones y @JsonIgnore simplemente no funcionaría. Esta biblioteca lo hizo muy simple.
Jeff
2
Hiciste que mi código fuera más limpio y más fácil de implementar. gracias
anindis
@monitorjbl: esto está un poco fuera de lugar, he usado vistas json y está resolviendo mi propósito. Pero no puedo registrar el serializador personalizado para la clase java.util.Date (sin tiempo de ejecución / error de tiempo de compilación), como para la cadena, pude registrar el serializador personalizado.
Ninad
17

¿Puedo hacerlo de forma dinámica?

Crear clase de vista:

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

Anotar su 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 la clase de vista en su 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);
    }

}

Ejemplo de datos:

{"id":"5aa2496df863482dc4da2067","name":"test","createdAt":"2018-03-10T09:35:31.050353800Z"}
Hett
fuente
1
¡Esta es una respuesta fantástica y minimalista! Quería regresar como JSON solo unos pocos campos de un componente anotado @Configuration y omitir todos los campos internos que se incluyen automáticamente. ¡Muchas gracias!
stx
15

Podemos hacer esto configurando el acceso a JsonProperty.Access.WRITE_ONLYmientras declaramos la propiedad.

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

Agregue @JsonInclude(JsonInclude.Include.NON_NULL)(obliga a Jackson a serializar valores nulos) a la clase y @JsonIgnoreal campo de contraseña.

Por supuesto, también puede configurar @JsonIgnorecreatedBy y updatedBy si siempre desea ignorar entonces y no solo en este caso específico.

ACTUALIZAR

En el caso de que no desee agregar la anotación al POJO en sí, una excelente opción son las Anotaciones Mixin de Jackson. Consulte la documentación

geo y
fuente
¿Puedo hacerlo de forma dinámica? ¿No estás en POJO? ¿Puedo hacerlo en mi clase de Controlador?
iCode
¿Quiere decir que no desea agregar las anotaciones al POJO?
geo y
Porque a veces es posible que desee enviar todos los campos al lado del cliente. A veces pocos campos. Los campos que debo enviar al lado del cliente se obtienen de la base de datos solo en la clase del controlador. Después de eso, solo necesito establecer qué campos deben ignorar.
iCode
12

Sí, puede especificar qué campos se serializan como respuesta JSON y cuáles ignorar. Esto es lo que debe hacer para implementar propiedades Ignorar dinámicamente.

1) Primero, debe agregar @JsonFilter de com.fasterxml.jackson.annotation.JsonFilter en su clase de entidad como.

import com.fasterxml.jackson.annotation.JsonFilter;

@JsonFilter("SomeBeanFilter")
public class SomeBean {

  private String field1;

  private String field2;

  private String field3;

  // getters/setters
}

2) Luego, en su controlador, debe agregar crear el objeto MappingJacksonValue y establecer filtros y, al final, debe devolver 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;
  }
}

Esto es lo que obtendrá en respuesta:

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

en lugar de esto:

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

Aquí puede ver que ignora otras propiedades (campo3 en este caso) en respuesta a excepción de la propiedad campo1 y campo2.

Espero que esto ayude.

Shafqat Shafi
fuente
1
@Shafqat Man, muchas gracias, eres mi salvador. Pasé casi un día intentando descubrir este tipo de funcionalidad. ¿Esta solución es tan elegante y sencilla? y hace exactamente lo que se le pidió. Debe marcarse como la respuesta correcta.
Oleg Kuts
6

Si yo fuera usted y quisiera hacerlo, no usaría mi entidad Usuario en la capa Controlador, sino que creo y uso UserDto (Objeto de transferencia de datos) para comunicarme con la capa (Servicio) comercial y el Controlador. Puede utilizar Apache BeanUtils (método copyProperties) para copiar datos de la entidad Usuario a UserDto.

Hamedz
fuente
3

He creado un JsonUtil que se puede usar para ignorar campos en tiempo de ejecución mientras se da una respuesta.

Ejemplo de uso: el primer argumento debe ser cualquier clase POJO (Estudiante) e ignoreFields son campos separados por comas que desea ignorar en respuesta.

 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
fuente
2

Lo resolví usando solo @JsonIgnorecomo lo sugirió @kryger. Entonces tu getter se convertirá en:

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

Puede establecer, @JsonIgnorepor supuesto, en campo, setter o getter como se describe aquí .

Y, si desea proteger la contraseña cifrada solo en el lado de la serialización (por ejemplo, cuando necesita iniciar sesión con sus usuarios), agregue esta @JsonPropertyanotación a su campo :

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

Más info aquí .

foxbit
fuente
1

Encontré una solución para mí con Spring y Jackson

Primero especifique el nombre del filtro en la entidad

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

}

Luego puede ver la clase de nombres de filtros de constantes con el FilterProvider predeterminado utilizado en la configuración 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);
}

}

Configuración de primavera:

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

}

}

Finalmente, puedo especificar un filtro específico en restConstoller cuando lo necesite ...

@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
fuente
5
por qué hay tantas complicaciones para una pregunta fácil
Humoyun Ahmad
1

Colóquelo @JsonIgnoreen el campo o su captador, o cree un dto personalizado

@JsonIgnore
private String encryptedPwd;

o como se mencionó anteriormente ceekayanotándolo con @JsonPropertydonde el atributo de acceso está configurado para escribir solo

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

No crearía un UserJsonResponse sería una solución más limpia clase y completar los campos deseados?

Devolver directamente un JSON parece una gran solución cuando desea devolver todo el modelo. De lo contrario, se vuelve complicado.

En el futuro, por ejemplo, es posible que desee tener un campo JSON que no coincida con ningún campo de modelo y luego se encontrará en un problema mayor.

Leonardo Beal
fuente
0

Esta es una herramienta de utilidad limpia para la respuesta anterior :

@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
fuente
-5

En su clase de entidad, agregue una @JsonInclude(JsonInclude.Include.NON_NULL)anotación para resolver el problema

se verá como

@Entity
@JsonInclude(JsonInclude.Include.NON_NULL)
Jeevan Gowda
fuente
Totalmente respondido de manera irrelevante. El propósito de la pregunta es diferente, mientras que la respuesta se trata de otra cosa. -1 por eso
Hammad Dar