Abaikan kolom dari objek Java secara dinamis saat mengirim sebagai JSON dari Spring MVC

105

Saya memiliki kelas model seperti ini, untuk hibernasi

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

Di Spring MVC controller, menggunakan DAO, saya bisa mendapatkan objek. dan kembali sebagai Objek 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;
    }
}

Lihat bagian dilakukan menggunakan AngularJS, sehingga akan mendapatkan JSON seperti ini

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

Jika saya tidak ingin menyetel Kata Sandi terenkripsi, saya akan menyetel bidang itu juga sebagai null.

Tetapi saya tidak ingin seperti ini, saya tidak ingin mengirim semua bidang ke sisi klien. Jika saya tidak ingin bidang sandi, diperbarui oleh, dibuat oleh dikirim, hasil saya JSON harus seperti

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

Daftar bidang yang tidak ingin saya kirim ke klien yang berasal dari tabel database lain. Jadi itu akan berubah berdasarkan pengguna yang masuk. Bagaimana saya bisa melakukannya?

Saya harap Anda mendapatkan pertanyaan saya.

iCode
sumber
Apa yang akan Anda katakan tentang jawaban ini? stackoverflow.com/a/30559076/3488143
Iryna
informasi ini mungkin berguna stackoverflow.com/questions/12505141/…
Musa

Jawaban:

143

Tambahkan @JsonIgnoreProperties("fieldname")anotasi ke POJO Anda.

Atau Anda dapat menggunakan @JsonIgnoresebelum nama bidang yang ingin Anda abaikan saat menonaktifkan JSON. Contoh:

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

Contoh GitHub

pengguna3145373 ツ
sumber
63
Bisakah saya melakukannya secara dinamis? Tidak di POJO? Bisakah saya melakukannya di kelas Controller saya?
iCode
3
@iProgrammer: berikut ini yang Anda inginkan: stackoverflow.com/questions/8179986/…
user3145373
3
@iProgrammer: jawaban yang sangat mengesankan di sini stackoverflow.com/questions/13764280/…
pengguna3145373 ツ
11
komentar: @JsonIgnoreadalah com.fasterxml.jackson.annotation.JsonIgnoretidak org.codehaus.jackson.annotate.JsonIgnore.
xiaohuo
5
Itu mengabaikan keduanya saat membaca dari permintaan dan saat mengirim respons. Saya ingin mengabaikan hanya saat mengirim tanggapan karena saya membutuhkan properti itu dari objek permintaan. Ada ide?
zulkarnain shah
32

Saya tahu saya agak terlambat ke pesta, tapi sebenarnya saya mengalami ini juga beberapa bulan yang lalu. Semua solusi yang tersedia tidak terlalu menarik bagi saya (mixin? Ugh!), Jadi saya akhirnya membuat perpustakaan baru untuk membuat proses ini lebih bersih. Tersedia di sini jika ada yang ingin mencobanya: https://github.com/monitorjbl/spring-json-view .

Penggunaan dasarnya cukup sederhana, Anda menggunakan JsonViewobjek dalam metode pengontrol Anda seperti ini:

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

Anda juga dapat menggunakannya di luar 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
sumber
5
Terima kasih! Ini adalah cara untuk pergi untukku. Saya membutuhkan tampilan JSON khusus dengan objek yang sama di lokasi berbeda dan @JsonIgnore tidak akan berfungsi. Perpustakaan ini membuatnya sangat sederhana untuk diselesaikan.
Jeff
2
Anda membuat kode saya lebih bersih dan implementasi lebih mudah. terima kasih
anindis
@monitorjbl: ini agak keluar jalur, saya telah menggunakan tampilan json dan itu menyelesaikan tujuan saya. Tetapi saya tidak dapat mendaftarkan serializer kustom untuk kelas java.util.Date (tidak ada runtime / kesalahan waktu kompilasi) sedangkan untuk string saya dapat mendaftarkan serializer kustom.
Ninad
17

Bisakah saya melakukannya secara dinamis?

Buat kelas tampilan:

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

Beri keterangan model Anda

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

Tentukan kelas tampilan di pengontrol Anda

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

}

Contoh data:

{"id":"5aa2496df863482dc4da2067","name":"test","createdAt":"2018-03-10T09:35:31.050353800Z"}
Hett
sumber
1
Ini adalah jawaban yang fantastis dan minimalis! Saya ingin mengembalikan sebagai JSON hanya beberapa bidang dari komponen beranotasi @Configuration dan melewatkan semua bidang internal yang secara otomatis disertakan. Terima kasih banyak!
stx
15

Kita dapat melakukan ini dengan menyetel akses ke JsonProperty.Access.WRITE_ONLYsaat mendeklarasikan properti.

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

Tambahkan @JsonInclude(JsonInclude.Include.NON_NULL)(memaksa Jackson untuk membuat serialisasi nilai null) ke kelas serta @JsonIgnorebidang sandi.

Anda tentu saja dapat mengatur @JsonIgnoredi createBy dan updatedBy juga jika Anda selalu ingin mengabaikannya dan tidak hanya dalam kasus khusus ini.

MEMPERBARUI

Jika Anda tidak ingin menambahkan anotasi ke POJO itu sendiri, pilihan yang bagus adalah Anotasi Mixin Jackson. Lihat dokumentasinya

geoand
sumber
Bisakah saya melakukannya secara dinamis? Tidak di POJO? Bisakah saya melakukannya di kelas Controller saya?
iCode
Apakah Anda bermaksud tidak ingin menambahkan anotasi ke POJO?
geoand
Karena Terkadang saya mungkin ingin mengirim semua bidang ke sisi klien. Terkadang sedikit bidang. Bidang yang harus saya kirim ke sisi klien didapat dari database di kelas pengontrol saja. Setelah itu hanya saya yang perlu mengatur bidang mana yang harus diabaikan.
iCode
12

Ya, Anda dapat menentukan bidang mana yang diserialkan sebagai respons JSON dan mana yang diabaikan. Inilah yang perlu Anda lakukan untuk mengimplementasikan properti abaikan secara dinamis.

1) Pertama, Anda perlu menambahkan @JsonFilter dari com.fasterxml.jackson.annotation.JsonFilter pada kelas entitas Anda sebagai.

import com.fasterxml.jackson.annotation.JsonFilter;

@JsonFilter("SomeBeanFilter")
public class SomeBean {

  private String field1;

  private String field2;

  private String field3;

  // getters/setters
}

2) Kemudian di controller Anda, Anda harus menambahkan membuat objek MappingJacksonValue dan mengatur filter di atasnya dan pada akhirnya, Anda harus mengembalikan objek ini.

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

Inilah yang akan Anda dapatkan sebagai tanggapan:

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

alih-alih ini:

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

Di sini Anda bisa melihatnya mengabaikan properti lain (bidang3 dalam kasus ini) sebagai respons kecuali untuk properti bidang1 dan bidang2.

Semoga ini membantu.

Shafqat Shafi
sumber
1
@ Shafqat Man, terima kasih banyak, Anda adalah penyelamat saya. Menghabiskan hampir satu hari mencoba mencari tahu fungsi semacam ini. Solusi ini sangat elegan dan sederhana? dan melakukan persis seperti yang diminta. Harus ditandai sebagai jawaban yang benar.
Oleg Kuts
6

Jika saya adalah Anda dan ingin melakukannya, saya tidak akan menggunakan entitas Pengguna saya di lapisan Pengontrol. Sebagai gantinya, saya membuat dan menggunakan UserDto (Objek transfer data) untuk berkomunikasi dengan lapisan bisnis (Layanan) dan Pengontrol. Anda dapat menggunakan Apache BeanUtils (metode copyProperties) untuk menyalin data dari entitas Pengguna ke UserDto.

Hamedz
sumber
3

Saya telah membuat JsonUtil yang dapat digunakan untuk mengabaikan bidang saat runtime sambil memberikan tanggapan.

Contoh Penggunaan: Argumen pertama harus berupa kelas POJO (Mahasiswa) dan ignoreFields adalah kolom yang dipisahkan koma yang ingin Anda abaikan sebagai respons.

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

Saya telah menyelesaikannya hanya dengan menggunakan @JsonIgnoreseperti yang disarankan @kryger. Jadi pengambil Anda akan menjadi:

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

Anda dapat mengatur @JsonIgnorekursus di lapangan, penyetel atau pengambil seperti dijelaskan di sini .

Dan, jika Anda ingin melindungi sandi terenkripsi hanya di sisi serialisasi (misalnya, saat Anda perlu masuk ke pengguna Anda), tambahkan @JsonPropertyanotasi ini ke bidang Anda :

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

Info selengkapnya di sini .

foxbit
sumber
1

Saya telah menemukan solusi untuk saya dengan Spring dan jackson

Pertama tentukan nama filter di entitas

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

}

Kemudian Anda dapat melihat konstanta filter nama kelas dengan FilterProvider default yang digunakan dalam konfigurasi pegas

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

}

Konfigurasi pegas:

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

}

}

Akhirnya saya dapat menentukan filter tertentu di restConstoller ketika saya membutuhkan ....

@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
sumber
5
mengapa begitu rumit untuk pertanyaan yang mudah
Humoyun Ahmad
1

Tempatkan @JsonIgnoredi lapangan atau pengambilnya, atau buat dto kustom

@JsonIgnore
private String encryptedPwd;

atau seperti yang disebutkan di atas dengan ceekaymemberi anotasi di @JsonPropertymana atribut akses disetel untuk hanya menulis

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

Bukankah membuat UserJsonResponsekelas dan mengisi dengan bidang yang diinginkan menjadi solusi yang lebih bersih?

Mengembalikan JSON secara langsung tampaknya merupakan solusi yang bagus ketika Anda ingin mengembalikan semua model. Kalau tidak, itu hanya akan menjadi berantakan.

Di masa mendatang, misalnya Anda mungkin ingin memiliki bidang JSON yang tidak cocok dengan bidang Model apa pun dan kemudian Anda berada dalam masalah yang lebih besar.

Leonardo Beal
sumber
0

Ini adalah alat utilitas bersih untuk jawaban di atas :

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

Di kelas entitas Anda, tambahkan @JsonInclude(JsonInclude.Include.NON_NULL)anotasi untuk menyelesaikan masalah

itu akan terlihat seperti

@Entity
@JsonInclude(JsonInclude.Include.NON_NULL)
Jeevan Gowda
sumber
Jawaban yang sama sekali tidak relevan. Tujuan dari pertanyaan berbeda sedangkan jawabannya adalah tentang hal lain. -1 untuk itu
Hammad Dar