Ignora i campi dall'oggetto Java in modo dinamico durante l'invio come JSON da Spring MVC


105

Ho una classe modello come questa, per l'ibernazione

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

Nel controller Spring MVC, utilizzando DAO, sono in grado di ottenere l'oggetto. e restituendo come oggetto 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 di visualizzazione viene eseguita utilizzando AngularJS, quindi otterrà JSON in questo modo

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

Se non voglio impostare la password crittografata, imposterò anche quel campo come null.

Ma non voglio in questo modo, non voglio inviare tutti i campi al lato client. Se non voglio che la password, updatedby, createdby campi da inviare, il mio risultato JSON dovrebbe essere come

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

L'elenco dei campi che non voglio inviare al client proveniente da un'altra tabella del database. Quindi cambierà in base all'utente che ha effettuato l'accesso. Come posso farlo?

Spero che tu abbia la mia domanda.


Cosa diresti di questa risposta? stackoverflow.com/a/30559076/3488143
Iryna

queste informazioni possono essere utili stackoverflow.com/questions/12505141/…
Musa

Risposte:


143

Aggiungi l' @JsonIgnoreProperties("fieldname")annotazione al tuo POJO.

Oppure puoi usare @JsonIgnoreprima del nome del campo che desideri ignorare durante la deserializzazione di JSON. Esempio:

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

Esempio di GitHub


63
Posso farlo dinamicamente? Non in POJO? Posso farlo nella mia classe Controller?
iCode

3
@iProgrammer: ecco un simile come si vuole: stackoverflow.com/questions/8179986/...
user3145373ツ

3
@iProgrammer: molto impressionante risposta qui stackoverflow.com/questions/13764280/...
user3145373ツ

11
osservazione: non lo @JsonIgnoreè . com.fasterxml.jackson.annotation.JsonIgnoreorg.codehaus.jackson.annotate.JsonIgnore
xiaohuo

5
Questo ignora sia durante la lettura dalla richiesta che durante l'invio della risposta. Voglio ignorare solo durante l'invio della risposta perché ho bisogno di quella proprietà dall'oggetto richiesta. Qualche idea?
zulkarnain shah

33

So di essere un po 'in ritardo alla festa, ma in realtà mi sono imbattuto anche in questo qualche mese fa. Tutte le soluzioni disponibili non mi erano molto allettanti (mixin? Ugh!), Quindi ho finito per creare una nuova libreria per rendere questo processo più pulito. È disponibile qui se qualcuno vuole provarlo: https://github.com/monitorjbl/spring-json-view .

L'utilizzo di base è piuttosto semplice, usi l' JsonViewoggetto nei metodi del tuo controller in questo modo:

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

Puoi anche usarlo al di fuori della primavera:

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

5
Grazie! Questa era la strada da percorrere per me. Avevo bisogno di visualizzazioni JSON personalizzate con gli stessi oggetti in posizioni diverse e @JsonIgnore semplicemente non funzionava. Questa libreria ha reso semplicissimo farlo.
Jeff

2
Hai reso il mio codice più pulito e più facile da implementare. grazie
anindis

@monitorjbl: questo è un po 'fuori strada, ho usato le visualizzazioni JSON e risolve il mio scopo. Ma non sono in grado di registrare il serializzatore personalizzato per la classe java.util.Date (nessun errore di runtime / tempo di compilazione) erano come per la stringa sono stato in grado di registrare il serializzatore personalizzato.
Ninad

18

Posso farlo dinamicamente?

Crea classe di visualizzazione:

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

Annota il tuo modello

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

Specificare la classe di visualizzazione nel controller

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

}

Esempio di dati:

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

1
Questa è una risposta fantastica e minimalista! Volevo tornare come JSON solo pochi campi da un componente annotato @Configuration e saltare tutti i campi interni che vengono inclusi automaticamente. Molte grazie!
stx

15

Possiamo farlo impostando l'accesso a JsonProperty.Access.WRITE_ONLYdurante la dichiarazione della proprietà.

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

12

Aggiungi @JsonInclude(JsonInclude.Include.NON_NULL)(forza Jackson a serializzare i valori nulli) alla classe e @JsonIgnoreal campo della password.

Puoi ovviamente impostare anche @JsonIgnorecreatedBy e updatedBy se vuoi ignorare sempre allora e non solo in questo caso specifico.

AGGIORNARE

Nel caso in cui non desideri aggiungere l'annotazione al POJO stesso, un'ottima opzione è Jackson's Mixin Annotations. Controlla la documentazione


Posso farlo dinamicamente? Non in POJO? Posso farlo nella mia classe Controller?
iCode

Vuoi dire che non vuoi aggiungere le annotazioni al POJO?
geoand

Perché a volte potrei voler inviare tutti i campi al lato client. A volte pochi campi. I campi che dovrei inviare al lato client vengono recuperati dal database solo nella classe controller. Dopodiché, devo solo impostare i campi da ignorare.
iCode

12

Sì, puoi specificare quali campi vengono serializzati come risposta JSON e quali ignorare. Questo è ciò che devi fare per implementare le proprietà Ignora dinamicamente.

1) Innanzitutto, devi aggiungere @JsonFilter da com.fasterxml.jackson.annotation.JsonFilter sulla tua classe entità come.

import com.fasterxml.jackson.annotation.JsonFilter;

@JsonFilter("SomeBeanFilter")
public class SomeBean {

  private String field1;

  private String field2;

  private String field3;

  // getters/setters
}

2) Quindi nel tuo controller, devi aggiungere creare l'oggetto MappingJacksonValue e impostare i filtri su di esso e alla fine, devi restituire questo oggetto.

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

Questo è ciò che otterrai in risposta:

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

Invece di questo:

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

Qui puoi vedere che ignora altre proprietà (campo3 in questo caso) in risposta ad eccezione delle proprietà campo1 e campo2.

Spero che questo ti aiuti.


1
@Shafqat Man, grazie mille, sei il mio salvatore. Ho trascorso quasi una giornata cercando di scoprire questo tipo di funzionalità. Questa soluzione è così elegante e semplice? e fa esattamente ciò che è stato richiesto. Dovrebbe essere contrassegnato come la risposta giusta.
Oleg Kuts

6

Se fossi in te e volessi farlo, non utilizzerei la mia entità Utente nel livello Controller, ma creerei e utilizzerei UserDto (Oggetto trasferimento dati) per comunicare con il livello aziendale (Servizio) e Controller. È possibile utilizzare Apache BeanUtils (metodo copyProperties) per copiare i dati dall'entità utente a UserDto.


3

Ho creato un JsonUtil che può essere utilizzato per ignorare i campi in fase di esecuzione mentre si fornisce una risposta.

Utilizzo di esempio: il primo argomento dovrebbe essere una classe POJO (Studente) e ignoreFields è un campo separato da virgole che si desidera ignorare in risposta.

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

2

Ho risolto usando solo @JsonIgnorecome @kryger ha suggerito. Quindi il tuo getter diventerà:

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

Puoi impostare @JsonIgnoreovviamente sul campo, setter o getter come descritto qui .

E, se desideri proteggere la password crittografata solo sul lato della serializzazione (ad esempio, quando devi accedere ai tuoi utenti), aggiungi questa @JsonPropertyannotazione al tuo campo :

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

Maggiori info qui .


1

Ho trovato una soluzione per me con Spring e Jackson

Specificare innanzitutto il nome del filtro nell'entità

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

}

Quindi puoi vedere le costanti dei nomi dei filtri della classe con il FilterProvider predefinito utilizzato nella configurazione di 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);
}

}

Configurazione a molla:

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

}

}

Alla fine posso specificare un filtro specifico in restConstoller quando ne ho bisogno ....

@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
perché così complicazioni per una domanda facile
Humoyun Ahmad

1

Posiziona @JsonIgnoresul campo o il suo getter, oppure crea un dto personalizzato

@JsonIgnore
private String encryptedPwd;

o, come accennato in precedenza, ceekayannotalo con @JsonPropertydove l'attributo di accesso è impostato per la sola scrittura

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

0

Creare una UserJsonResponseclasse e popolare i campi desiderati non sarebbe una soluzione più pulita?

Restituire direttamente un JSON sembra un'ottima soluzione quando si vuole restituire tutto il modello. Altrimenti diventa solo disordinato.

In futuro, ad esempio, potresti voler avere un campo JSON che non corrisponde a nessun campo Modello e quindi sei in guai più grossi.


0

Questo è uno strumento di utilità pulito per la risposta sopra :

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

-5

Nella tua classe di entità aggiungi l' @JsonInclude(JsonInclude.Include.NON_NULL)annotazione per risolvere il problema

sembrerà

@Entity
@JsonInclude(JsonInclude.Include.NON_NULL)

Rispose totalmente irrilevante. Lo scopo della domanda è diverso mentre la risposta riguarda qualcos'altro. -1 per quello
Hammad Dar
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.