Ignoruj ​​dynamicznie pola z obiektu Java podczas wysyłania jako JSON ze Spring MVC

105

Mam taką klasę modelu do hibernacji

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

W kontrolerze Spring MVC za pomocą DAO jestem w stanie pobrać obiekt. i zwracając jako obiekt 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;
    }
}

Część widoku jest wykonywana za pomocą AngularJS, więc otrzyma taki kod JSON

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

Jeśli nie chcę ustawiać zaszyfrowanego hasła, ustawię to pole również na null.

Ale nie chcę tego, nie chcę wysyłać wszystkich pól po stronie klienta. Jeśli nie chcę hasła, aktualizowanego przez, utworzonego przez pola do wysłania, mój wynik JSON powinien być podobny

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

Lista pól, których nie chcę wysyłać do klienta pochodząca z innej tabeli bazy danych. Więc będzie się to zmieniać w zależności od zalogowanego użytkownika. Jak mogę to zrobić?

Mam nadzieję, że masz moje pytanie.

iCode
źródło
Co byś powiedział o tej odpowiedzi? stackoverflow.com/a/30559076/3488143
Iryna
ta informacja może być pomocna stackoverflow.com/questions/12505141/…
Musa

Odpowiedzi:

143

Dodaj @JsonIgnoreProperties("fieldname")adnotację do swojego POJO.

Lub możesz użyć @JsonIgnoreprzed nazwą pola, które chcesz zignorować podczas deserializacji JSON. Przykład:

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

Przykład GitHub

user3145373 ツ
źródło
63
Czy mogę to robić dynamicznie? Nie w POJO? Czy mogę to zrobić w mojej klasie kontrolera?
iCode
3
@iProgrammer: tutaj jest podobnie, jak chcesz: stackoverflow.com/questions/8179986/ ...
user3145373 ツ
3
@iProgrammer: bardzo imponująca odpowiedź tutaj stackoverflow.com/questions/13764280/ ...
user3145373 ツ
11
uwaga: @JsonIgnoreto com.fasterxml.jackson.annotation.JsonIgnorenie org.codehaus.jackson.annotate.JsonIgnore.
xiaohuo
5
To ignoruje zarówno podczas odczytu z żądania, jak i podczas wysyłania odpowiedzi. Chcę ignorować tylko podczas wysyłania odpowiedzi, ponieważ potrzebuję tej właściwości z obiektu żądania. Jakieś pomysły?
zulkarnain shah
33

Wiem, że jestem trochę spóźniony na imprezę, ale tak naprawdę spotkałem to kilka miesięcy temu. Wszystkie dostępne rozwiązania nie były dla mnie zbyt atrakcyjne (mieszanki? Hmm!), Więc ostatecznie stworzyłem nową bibliotekę, aby ten proces był czystszy. Jest dostępny tutaj, jeśli ktoś chciałby go wypróbować: https://github.com/monitorjbl/spring-json-view .

Podstawowe użycie jest dość proste, używasz JsonViewobiektu w metodach kontrolera w następujący sposób:

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

Możesz go również używać poza wiosną:

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
źródło
5
Dziękuję Ci! To była droga dla mnie. Potrzebowałem niestandardowych widoków JSON z tymi samymi obiektami w różnych lokalizacjach, a @JsonIgnore po prostu nie działał. Ta biblioteka sprawiła, że ​​zrobienie tego było naprawdę proste.
Jeff,
2
Uczyniłeś mój kod czystszym i łatwiejszym w implementacji. dziękuję
anindis
@monitorjbl: jest to trochę nieaktualne, użyłem widoków json i rozwiązałem mój cel. Ale nie jestem w stanie zarejestrować niestandardowego serializatora dla klasy java.util.Date (brak błędu czasu wykonania / kompilacji) tak, jak w przypadku ciągu znaków, mogłem zarejestrować niestandardowy serializator.
Ninad
18

Czy mogę to robić dynamicznie?

Utwórz klasę widoku:

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

Opisz swój model

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

Określ klasę widoku w kontrolerze

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

}

Przykład danych:

{"id":"5aa2496df863482dc4da2067","name":"test","createdAt":"2018-03-10T09:35:31.050353800Z"}
Hett
źródło
1
To fantastyczna i minimalistyczna odpowiedź! Chciałem wrócić jako JSON tylko kilka pól z komponentu z adnotacjami @Configuration i pominąć wszystkie pola wewnętrzne, które są automatycznie uwzględniane. Wielkie dzięki!
stx
15

Możemy to zrobić, ustawiając dostęp do JsonProperty.Access.WRITE_ONLYpodczas deklarowania właściwości.

@JsonProperty( value = "password", access = JsonProperty.Access.WRITE_ONLY)
@SerializedName("password")
private String password;
ceekay
źródło
12

Dodaj @JsonInclude(JsonInclude.Include.NON_NULL)(wymusza na Jacksonie serializowanie wartości null) do klasy, a także @JsonIgnoredo pola hasła.

Możesz oczywiście ustawić @JsonIgnorena createdBy i updatedBy, jeśli zawsze chcesz ignorować to, a nie tylko w tym konkretnym przypadku.

AKTUALIZACJA

Jeśli nie chcesz dodawać adnotacji do samego POJO, świetną opcją jest Mixin Annotations Jacksona. Sprawdź dokumentację

geoand
źródło
Czy mogę to robić dynamicznie? Nie w POJO? Czy mogę to zrobić w mojej klasie kontrolera?
iCode
Czy masz na myśli, że nie chcesz dodawać adnotacji do POJO?
geoand
Ponieważ czasami mogę chcieć wysłać wszystkie pola po stronie klienta. Czasami kilka pól. Pola, które powinienem wysłać do klienta, są pobierane z bazy danych tylko w klasie kontrolera. Potem tylko muszę ustawić, które pola mają być ignorowane.
iCode
12

Tak, możesz określić, które pola są serializowane jako odpowiedź JSON, a które zignorować. Oto, co musisz zrobić, aby zaimplementować Dynamicznie ignoruj ​​właściwości.

1) Najpierw musisz dodać @JsonFilter z com.fasterxml.jackson.annotation.JsonFilter do swojej klasy encji jako.

import com.fasterxml.jackson.annotation.JsonFilter;

@JsonFilter("SomeBeanFilter")
public class SomeBean {

  private String field1;

  private String field2;

  private String field3;

  // getters/setters
}

2) Następnie w kontrolerze musisz dodać stwórz obiekt MappingJacksonValue i ustawić na nim filtry, a na koniec musisz zwrócić ten obiekt.

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

Oto, co otrzymasz w odpowiedzi:

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

zamiast tego:

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

Tutaj możesz zobaczyć, że ignoruje inne właściwości (w tym przypadku pole3) w odpowiedzi, z wyjątkiem właściwości field1 i field2.

Mam nadzieję że to pomoże.

Shafqat Shafi
źródło
1
@Shafqat Człowieku, bardzo dziękuję, jesteś moim wybawcą. Spędziłem prawie dzień, próbując znaleźć tego rodzaju funkcje. Takie rozwiązanie jest tak eleganckie i proste? i robi dokładnie to, o co proszono. Powinien być oznaczony jako prawidłowa odpowiedź.
Oleg Kuts
6

Gdybym był Tobą i chciałbym to zrobić, nie używałbym swojej encji użytkownika w warstwie kontrolera, zamiast tego tworzę i używam UserDto (obiekt transferu danych) do komunikacji z warstwą biznesową (usługową) i Administratorem. Możesz użyć Apache BeanUtils (metoda copyProperties), aby skopiować dane z jednostki User do UserDto.

Hamedz
źródło
3

Utworzyłem JsonUtil, który może służyć do ignorowania pól w czasie wykonywania podczas udzielania odpowiedzi.

Przykład zastosowania: Pierwszy argument powinien być dowolną klasą POJO (Student), a ignoreFields to pola oddzielone przecinkami, które chcesz zignorować w odpowiedzi.

 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
źródło
2

Rozwiązałem, używając tylko @JsonIgnoretego, co zasugerował @kryger. Więc twój getter stanie się:

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

Możesz ustawić @JsonIgnoreoczywiście na pole, setter lub getter, jak opisano tutaj .

A jeśli chcesz chronić zaszyfrowane hasło tylko po stronie serializacji (np. Kiedy musisz zalogować się do swoich użytkowników), dodaj tę @JsonPropertyadnotację do swojego pola :

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

Więcej informacji tutaj .

foxbit
źródło
1

Znalazłem dla mnie rozwiązanie dzięki Springowi i Jacksonowi

Najpierw określ nazwę filtru w encji

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

}

Następnie możesz zobaczyć klasy nazw filtrów stałych z domyślnym filtrem FilterProvider używanym w konfiguracji sprężynowej

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

}

Konfiguracja sprężyn:

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

}

}

Ostatecznie mogę określić konkretny filtr w restConstoller, kiedy potrzebuję ....

@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
źródło
5
skąd
1

Umieść @JsonIgnorena polu lub jego getterze lub stwórz własne dto

@JsonIgnore
private String encryptedPwd;

lub jak wspomniano powyżej, ceekaydodając do niego adnotację, @JsonPropertygdzie atrybut dostępu jest ustawiony na tylko zapis

@JsonProperty( value = "password", access = JsonProperty.Access.WRITE_ONLY)
private String encryptedPwd;
Alan Sereb
źródło
0

Czy utworzenie UserJsonResponseklasy i wypełnienie żądanymi polami nie byłoby czystszym rozwiązaniem?

Zwracanie bezpośrednio JSON wydaje się świetnym rozwiązaniem, gdy chcesz oddać cały model. W przeciwnym razie robi się po prostu bałagan.

Na przykład w przyszłości możesz chcieć mieć pole JSON, które nie pasuje do żadnego pola Model, a wtedy masz większe kłopoty.

Leonardo Beal
źródło
0

To jest czyste narzędzie użytkowe dla powyższej odpowiedzi :

@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
źródło
-5

W swojej klasie encji dodaj @JsonInclude(JsonInclude.Include.NON_NULL)adnotację, aby rozwiązać problem

będzie wyglądać

@Entity
@JsonInclude(JsonInclude.Include.NON_NULL)
Jeevan Gowda
źródło
Zupełnie nieistotnie odpowiedział. Cel pytania jest inny, podczas gdy odpowiedź dotyczy czegoś innego. -1 za to
Hammad Dar