Spring MVC'den JSON olarak gönderirken Java nesnesindeki alanları dinamik olarak yoksay


105

Hazırda bekletme için böyle bir model sınıfım var

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

Spring MVC kontrolcüsünde DAO kullanarak nesneyi alabiliyorum. ve JSON Nesnesi olarak geri dönüyor.

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

Görünüm bölümü AngularJS kullanılarak yapıldı, bu yüzden JSON'u böyle alacak

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

Şifreli Parola ayarlamak istemiyorsam, bu alanı da boş olarak ayarlayacağım.

Ama ben böyle istemiyorum, tüm alanları istemci tarafına göndermek istemiyorum. Alanlar tarafından oluşturulan parola gönderilmesini istemiyorsam, sonucum JSON şöyle olmalıdır:

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

Diğer veritabanı tablosundan gelen istemciye göndermek istemediğim alanların listesi. Yani giriş yapan kullanıcıya göre değişecek. Bunu nasıl yapabilirim?

Umarım sorumu almışsındır.


Bu cevap hakkında ne söylersiniz? stackoverflow.com/a/30559076/3488143
Iryna

bu bilgi yardımcı olabilir stackoverflow.com/questions/12505141/…
Musa

Yanıtlar:


143

Ekle @JsonIgnoreProperties("fieldname")sizin POJO için ek açıklama.

Veya @JsonIgnoreJSON serisini kaldırırken yok saymak istediğiniz alanın adından önce kullanabilirsiniz . Misal:

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

GitHub örneği


63
Bunu dinamik olarak yapabilir miyim? POJO'da yok mu? Bunu Controller sınıfımda yapabilir miyim?
iCode

3
@iProgrammer: işte istediğiniz gibi bir benzer: stackoverflow.com/questions/8179986/…
user3145373 ツ

3
@iProgrammer: çok etkileyici cevap burada stackoverflow.com/questions/13764280/…
user3145373 ツ

11
sözler: @JsonIgnoreolduğu com.fasterxml.jackson.annotation.JsonIgnoredeğil org.codehaus.jackson.annotate.JsonIgnore.
xiaohuo

5
Bu, hem istekten okurken hem de yanıt gönderirken göz ardı eder. İstek nesnesinden bu özelliğe ihtiyacım olduğu için yalnızca yanıt gönderirken yok saymak istiyorum. Herhangi bir fikir?
zulkarnain shah

33

Partiye biraz geç kaldığımı biliyorum ama aslında birkaç ay önce bununla da karşılaştım. Mevcut tüm çözümler bana pek çekici gelmedi (mixins? Ugh!), Bu yüzden bu süreci daha temiz hale getirmek için yeni bir kitaplık oluşturdum. Denemek isteyen varsa burada mevcuttur: https://github.com/monitorjbl/spring-json-view .

Temel kullanım oldukça basittir, JsonViewnesneyi denetleyici yöntemlerinizdeki gibi kullanırsınız:

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

Bahar dışında da kullanabilirsiniz:

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
Teşekkür ederim! Benim için gitmenin yolu buydu. Farklı konumlarda aynı nesnelerle özel JSON görünümlerine ihtiyacım vardı ve @JsonIgnore işe yaramazdı. Bu kütüphane halletmeyi çok basit hale getirdi.
Jeff

2
Kodumu daha temiz ve daha kolay hale getirdiniz. teşekkür ederim
anindis

@monitorjbl: Bu biraz yanlış, json görünümlerini ve amacımı çözmeyi kullandım. Ancak java.util.Date sınıfı için özel serileştiriciyi kaydedemiyorum (çalışma zamanı / derleme zamanı hatası yok) string için olduğu gibi özel serileştiriciyi kaydedebildim.
Ninad

18

Bunu dinamik olarak yapabilir miyim?

Görünüm sınıfı oluşturun:

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

Modelinize açıklama ekleyin

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

Denetleyicinizde görünüm sınıfını belirtin

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

}

Veri örneği:

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

1
Bu harika ve minimalist bir cevap! Bir @Configuration açıklamalı bileşenden yalnızca birkaç alan ve otomatik olarak dahil edilen tüm dahili alanları atlayarak JSON olarak dönmek istedim. Çok teşekkürler!
stx

15

Bunu JsonProperty.Access.WRITE_ONLYözelliği bildirirken erişim ayarlayarak yapabiliriz .

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

12

Parola alanına @JsonInclude(JsonInclude.Include.NON_NULL)olduğu kadar sınıfa ekleyin (Jackson'ı boş değerleri serileştirmeye zorlar) @JsonIgnore.

Tabii ki @JsonIgnore, yalnızca bu özel durumda değil, her zaman görmezden gelmek istiyorsanız, createBy ve updatedBy'yi de ayarlayabilirsiniz .

GÜNCELLEME

Notu POJO'nun kendisine eklemek istemiyorsanız, harika bir seçenek Jackson's Mixin Annotations'dır. Belgelere göz atın


Bunu dinamik olarak yapabilir miyim? POJO'da yok mu? Bunu Controller sınıfımda yapabilir miyim?
iCode

Ek açıklamaları POJO'ya eklemek istemediğinizi mi söylüyorsunuz?
geoand

Çünkü bazen tüm alanları müşteri tarafına göndermek isteyebilirim. Bazen birkaç alan. İstemci tarafına göndermem gereken alanlar sadece controller sınıfındaki veritabanından geliyor. Bundan sonra sadece hangi alanların yok sayılacağını ayarlamam gerekiyor.
iCode

12

Evet, hangi alanların JSON yanıtı olarak serileştirileceğini ve hangilerinin yoksayılacağını belirtebilirsiniz. Dinamik olarak yok sayma özelliklerini uygulamak için yapmanız gereken budur.

1) Öncelikle, varlık sınıfınıza com.fasterxml.jackson.annotation.JsonFilter içinden @JsonFilter eklemeniz gerekir.

import com.fasterxml.jackson.annotation.JsonFilter;

@JsonFilter("SomeBeanFilter")
public class SomeBean {

  private String field1;

  private String field2;

  private String field3;

  // getters/setters
}

2) Daha sonra denetleyicinizde, MappingJacksonValue nesnesini oluşturup üzerinde filtreler ayarlamanız ve sonunda bu nesneyi geri döndürmeniz gerekir.

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

Yanıt olarak alacağınız şey bu:

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

bunun yerine:

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

Burada, özellik field1 ve field2 hariç diğer özellikleri (bu durumda field3) yok saydığını görebilirsiniz.

Bu yardımcı olur umarım.


1
@Shafqat Man, çok teşekkür ederim, sen benim kurtarıcımsın. Bu tür bir işlevselliği bulmak için neredeyse bir gün geçirdim. Bu çözüm çok zarif ve basit mi? ve tam olarak isteneni yapar. Doğru cevap olarak işaretlenmelidir.
Oleg Kuts

6

Yerinizde olsam ve bunu yapmak isteseydim, Kullanıcı varlığımı Denetleyici katmanında kullanmazdım, bunun yerine iş (Hizmet) katmanı ve Denetleyici ile iletişim kurmak için UserDto (Veri aktarım nesnesi) oluşturup kullanırım. Kullanıcı varlığından UserDto'ya veri kopyalamak için Apache BeanUtils'i (copyProperties yöntemi) kullanabilirsiniz.


3

Bir yanıt verirken çalışma zamanında alanları yok saymak için kullanılabilecek bir JsonUtil oluşturdum.

Örnek Kullanım: İlk bağımsız değişken herhangi bir POJO sınıfı (Öğrenci) olmalı ve ignoreFields, yanıt olarak yok saymak istediğiniz virgülle ayrılmış alanlardır.

 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

Sadece @JsonIgnore@kryger'ın önerdiği gibi çözdüm. Böylece alıcınız:

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

@JsonIgnoreTabii ki burada açıklandığı gibi tarla, ayarlayıcı veya alıcı üzerinde ayarlayabilirsiniz .

Yalnızca seri tarafında (örneğin kullanıcılarınıza giriş gerektiğinde) şifreli şifreyi korumak istiyorsanız, bu eklenti @JsonPropertysizin için ek açıklama alanına :

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

Daha fazla bilgi burada .


1

Spring ve Jackson ile benim için bir çözüm buldum

Öncelikle varlıktaki filtre adını belirtin

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

}

Ardından, yay yapılandırmasında kullanılan varsayılan FilterProvider ile sabitler filtreleri ad sınıfını görebilirsiniz.

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

}

Yay konfigürasyonu:

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

}

}

Sonunda, restConstoller'da ihtiyacım olduğunda belirli bir filtre belirleyebilirim ....

@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
kolay bir soru için neden bu kadar karmaşıklık var
Humoyun Ahmad

1

Yeri @JsonIgnoresahada veya alıcı veya özel bir dto oluşturmak

@JsonIgnore
private String encryptedPwd;

veya yukarıda belirtildiği gibi, erişim özniteliğinin yalnızca yazılacak şekilde ayarlandığı yere ceekayaçıklama ekleyerek@JsonProperty

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

0

Bir UserJsonResponsesınıf oluşturmak ve istenen alanlarla doldurmak daha temiz bir çözüm olmaz mı?

Doğrudan bir JSON döndürmek, tüm modeli geri vermek istediğinizde harika bir çözüm gibi görünüyor. Aksi takdirde işler karışır.

Gelecekte, örneğin herhangi bir Model alanıyla eşleşmeyen bir JSON alanına sahip olmak isteyebilirsiniz ve o zaman daha büyük bir başınız belaya girer.


0

Bu, yukarıdaki cevap için temiz bir yardımcı araçtır :

@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

Varlık sınıfınızda @JsonInclude(JsonInclude.Include.NON_NULL)sorunu çözmek için ek açıklama ekleyin

gibi görünecek

@Entity
@JsonInclude(JsonInclude.Include.NON_NULL)

Tamamen alakasız bir şekilde cevaplandı. Cevap başka bir şey hakkındayken sorunun amacı farklıdır. -1 bunun için
Hammad Dar
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.