Ignorer les champs de l'objet Java de manière dynamique lors de l'envoi en tant que JSON depuis Spring MVC

105

J'ai une classe de modèle comme celle-ci, pour l'hibernation

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

Dans le contrôleur Spring MVC, en utilisant DAO, je suis capable d'obtenir l'objet. et retour en tant qu'objet 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 vue de la pièce se fait à l'aide d'AngularJS, elle obtiendra donc JSON comme ceci

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

Si je ne veux pas définir un mot de passe chiffré, je définirai également ce champ comme nul.

Mais je ne veux pas comme ça, je ne veux pas envoyer tous les champs côté client. Si je ne veux pas de mot de passe, mis à jour par, créé par les champs à envoyer, mon résultat JSON devrait être comme

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

La liste des champs que je ne veux pas envoyer au client provenant d'une autre table de base de données. Cela changera donc en fonction de l'utilisateur connecté. Comment puis-je faire cela?

J'espère que vous avez ma question.

iCode
la source
Que diriez-vous de cette réponse? stackoverflow.com/a/30559076/3488143
Iryna
cette information peut être utile stackoverflow.com/questions/12505141/…
Musa

Réponses:

143

Ajoutez l' @JsonIgnoreProperties("fieldname")annotation à votre POJO.

Ou vous pouvez utiliser @JsonIgnoreavant le nom du champ que vous souhaitez ignorer lors de la désérialisation de JSON. Exemple:

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

Exemple GitHub

user3145373 ツ
la source
63
Puis-je le faire dynamiquement? Pas dans POJO? Puis-je le faire dans ma classe de contrôleur?
iCode
3
@iProgrammer: voici un similaire que vous voulez: stackoverflow.com/questions/8179986/…
user3145373 ツ
3
@iProgrammer: réponse très impressionnante ici stackoverflow.com/questions/13764280/…
user3145373 ツ
11
remarque: @JsonIgnoren'est com.fasterxml.jackson.annotation.JsonIgnorepas org.codehaus.jackson.annotate.JsonIgnore.
xiaohuo
5
Cela ignore à la fois lors de la lecture de la demande et lors de l'envoi de la réponse. Je veux ignorer uniquement lors de l'envoi de la réponse car j'ai besoin de cette propriété de l'objet de requête. Des idées?
zulkarnain shah
33

Je sais que je suis un peu en retard à la fête, mais je suis tombé sur cela aussi il y a quelques mois. Toutes les solutions disponibles ne m'attiraient pas beaucoup (mixins? Ugh!), Alors j'ai fini par créer une nouvelle bibliothèque pour rendre ce processus plus propre. Il est disponible ici si quelqu'un souhaite l'essayer: https://github.com/monitorjbl/spring-json-view .

L'utilisation de base est assez simple, vous utilisez l' JsonViewobjet dans vos méthodes de contrôleur comme ceci:

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

Vous pouvez également l'utiliser en dehors du printemps:

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
la source
5
Je vous remercie! C'était la voie à suivre pour moi. J'avais besoin de vues JSON personnalisées avec les mêmes objets à différents endroits et @JsonIgnore ne fonctionnerait tout simplement pas. Cette bibliothèque a rendu la tâche extrêmement simple.
Jeff
2
Vous avez simplifié mon code et sa mise en œuvre. merci
anindis
@monitorjbl: c'est un peu hors piste, j'ai utilisé des vues json et cela résout mon objectif. Mais je ne suis pas en mesure d'enregistrer le sérialiseur personnalisé pour la classe java.util.Date (pas d'erreur d'exécution / de compilation), comme pour la chaîne, j'ai pu enregistrer le sérialiseur personnalisé.
Ninad
18

Puis-je le faire dynamiquement?

Créer une classe de vue:

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

Annoter votre modèle

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

Spécifiez la classe de vue dans votre contrôleur

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

}

Exemple de données:

{"id":"5aa2496df863482dc4da2067","name":"test","createdAt":"2018-03-10T09:35:31.050353800Z"}
Hett
la source
1
C'est une réponse fantastique et minimaliste! Je voulais retourner en tant que JSON seulement quelques champs d'un composant annoté @Configuration et ignorer tous les champs internes qui sont automatiquement inclus. Merci beaucoup!
stx
15

Nous pouvons le faire en définissant access sur JsonProperty.Access.WRITE_ONLYlors de la déclaration de la propriété.

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

Ajoutez @JsonInclude(JsonInclude.Include.NON_NULL)(force Jackson à sérialiser les valeurs nulles) à la classe ainsi qu'au @JsonIgnorechamp de mot de passe.

Vous pourriez bien sûr définir @JsonIgnore sur createdBy et updatedBy également si vous voulez toujours ignorer alors et pas seulement dans ce cas spécifique.

METTRE À JOUR

Dans le cas où vous ne souhaitez pas ajouter l'annotation au POJO lui-même, une excellente option est les annotations Mixin de Jackson. Consultez la documentation

geoand
la source
Puis-je le faire dynamiquement? Pas dans POJO? Puis-je le faire dans ma classe de contrôleur?
iCode
Voulez-vous dire que vous ne souhaitez pas ajouter les annotations au POJO?
geoand
Parce que parfois, je peux vouloir envoyer tous les champs côté client. Parfois peu de champs. Les champs que je devrais envoyer au côté client proviennent uniquement de la base de données de la classe de contrôleur. Après cela, je dois seulement définir les champs à ignorer.
iCode
12

Oui, vous pouvez spécifier quels champs sont sérialisés en tant que réponse JSON et lesquels ignorer. C'est ce que vous devez faire pour implémenter les propriétés Ignorer dynamiquement.

1) Tout d'abord, vous devez ajouter @JsonFilter de com.fasterxml.jackson.annotation.JsonFilter sur votre classe d'entité en tant que.

import com.fasterxml.jackson.annotation.JsonFilter;

@JsonFilter("SomeBeanFilter")
public class SomeBean {

  private String field1;

  private String field2;

  private String field3;

  // getters/setters
}

2) Ensuite, dans votre contrôleur, vous devez ajouter créer l'objet MappingJacksonValue et définir des filtres dessus et à la fin, vous devez retourner cet objet.

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

Voici ce que vous obtiendrez en réponse:

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

au lieu de cela:

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

Ici, vous pouvez voir qu'il ignore les autres propriétés (field3 dans ce cas) en réponse à l'exception de la propriété field1 et field2.

J'espère que cela t'aides.

Shafqat Shafi
la source
1
@Shafqat Man, merci beaucoup, tu es mon sauveur. J'ai passé presque une journée à essayer de découvrir ce type de fonctionnalité. Cette solution est si élégante et simple? et fait exactement ce qui a été demandé. Doit être marqué comme la bonne réponse.
Oleg Kuts
6

Si j'étais vous et que je voulais le faire, je n'utiliserais pas mon entité User dans la couche Controller. Vous pouvez utiliser Apache BeanUtils (méthode copyProperties) pour copier les données de l'entité User vers UserDto.

Hamedz
la source
3

J'ai créé un JsonUtil qui peut être utilisé pour ignorer les champs lors de l'exécution tout en donnant une réponse.

Exemple d'utilisation: Le premier argument doit être n'importe quelle classe POJO (Student) et ignoreFields est des champs séparés par des virgules que vous voulez ignorer en réponse.

 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
la source
2

J'ai résolu en utilisant uniquement @JsonIgnorecomme @kryger l'a suggéré. Ainsi votre getter deviendra:

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

Vous pouvez @JsonIgnorebien sûr définir sur le terrain, le setter ou le getter comme décrit ici .

Et, si vous souhaitez protéger le mot de passe crypté uniquement du côté de la sérialisation (par exemple lorsque vous devez vous connecter à vos utilisateurs), ajoutez cette @JsonPropertyannotation à votre champ :

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

Plus d'infos ici .

foxbit
la source
1

J'ai trouvé une solution pour moi avec Spring et Jackson

Spécifiez d'abord le nom du filtre dans l'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);

}

Ensuite, vous pouvez voir la classe de noms de filtres de constantes avec le FilterProvider par défaut utilisé dans la configuration de ressort

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

}

Configuration du ressort:

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

}

}

Enfin, je peux spécifier un filtre spécifique dans restConstoller quand j'ai besoin ...

@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
la source
5
pourquoi tant de complications pour une question facile
Humoyun Ahmad
1

Placez @JsonIgnoresur le champ ou son getter, ou créez un dto personnalisé

@JsonIgnore
private String encryptedPwd;

ou comme mentionné ci-dessus en l' ceekayannotant avec @JsonPropertyoù l'attribut d'accès est défini pour écrire uniquement

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

Ne créerait pas un UserJsonResponse classe et le peuplement des champs voulus ne serait-elle pas une solution plus propre?

Renvoyer directement un JSON semble une excellente solution lorsque vous souhaitez rendre tout le modèle. Sinon, ça devient juste compliqué.

À l'avenir, par exemple, vous voudrez peut-être avoir un champ JSON qui ne correspond à aucun champ de modèle et vous aurez alors un plus gros problème.

Léonard Beal
la source
0

Ceci est un outil utilitaire propre pour la réponse ci-dessus :

@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
la source
-5

Dans votre classe d'entité, ajoutez une @JsonInclude(JsonInclude.Include.NON_NULL)annotation pour résoudre le problème

ça ressemblera à

@Entity
@JsonInclude(JsonInclude.Include.NON_NULL)
Jeevan Gowda
la source
Réponse totalement hors de propos. Le but de la question est différent tandis que la réponse porte sur autre chose. -1 pour ça
Hammad Dar