Gson: Cara mengecualikan bidang tertentu dari Serialisasi tanpa penjelasan

413

Saya mencoba belajar Gson dan saya berjuang dengan pengecualian lapangan. Ini kelas saya

public class Student {    
  private Long                id;
  private String              firstName        = "Philip";
  private String              middleName       = "J.";
  private String              initials         = "P.F";
  private String              lastName         = "Fry";
  private Country             country;
  private Country             countryOfBirth;
}

public class Country {    
  private Long                id;
  private String              name;
  private Object              other;
}

Saya dapat menggunakan GsonBuilder dan menambahkan ExclusionStrategy untuk nama bidang seperti firstNameatau countrytetapi saya tidak bisa mengatur untuk mengecualikan properti dari bidang tertentu seperti country.name.

Menggunakan metode ini public boolean shouldSkipField(FieldAttributes fa), FieldAttributes tidak mengandung informasi yang cukup untuk mencocokkan bidang dengan filter seperti country.name.

PS: Saya ingin menghindari anotasi karena saya ingin memperbaiki ini dan menggunakan RegEx untuk menyaring bidang.

Sunting : Saya mencoba melihat apakah mungkin untuk meniru perilaku plugin Struts2 JSON

menggunakan Gson

<interceptor-ref name="json">
  <param name="enableSMD">true</param>
  <param name="excludeProperties">
    login.password,
    studentList.*\.sin
  </param>
</interceptor-ref>

Sunting: Saya membuka kembali pertanyaan dengan tambahan berikut:

Saya menambahkan bidang kedua dengan tipe yang sama untuk lebih memperjelas masalah ini. Pada dasarnya saya ingin mengecualikan country.nametetapi tidak countrOfBirth.name. Saya juga tidak ingin mengecualikan Negara sebagai tipe. Jadi jenisnya sama, itu adalah tempat aktual di objek grafik yang ingin saya tunjuk dan kecualikan.

Liviu T.
sumber
1
Masih pada versi 2.2 saya masih tidak dapat menentukan jalur untuk dikecualikan. flexjson.sourceforge.net terasa seperti alternatif yang baik.
Liviu T.
Lihat jawaban saya untuk pertanyaan yang cukup mirip. Ini didasarkan pada pembuatan kustom JsonSerializeruntuk beberapa jenis - Countrydalam kasus Anda - yang kemudian diterapkan ExclusionStrategyyang memutuskan bidang apa untuk diserialisasi.
pirho

Jawaban:

625

Setiap bidang yang tidak Anda inginkan serialisasi secara umum Anda harus menggunakan pengubah "transient", dan ini juga berlaku untuk json serializers (setidaknya itu berlaku untuk beberapa yang telah saya gunakan, termasuk gson).

Jika Anda tidak ingin nama muncul di serial json, berikan kata kunci sementara, misalnya:

private transient String name;

Lebih detail dalam dokumentasi Gson

Chris Seline
sumber
6
itu hampir sama dengan penjelasan pengecualian seperti di dalamnya berlaku untuk semua instance dari kelas itu. Saya ingin pengecualian dinamis runtime. Saya beberapa kasus saya ingin beberapa bidang dikecualikan untuk memberikan respon yang lebih ringan / terbatas dan dalam kasus lain saya ingin objek lengkap serial
Liviu T.
34
Satu hal yang perlu diperhatikan adalah efek transien baik serialisasi dan deserialisasi. Ini juga akan memancarkan nilai dari serial ke objek, bahkan jika itu ada di JSON.
Kong
3
Masalah dengan menggunakan transientalih-alih @Exposeadalah bahwa Anda masih harus mengejek POJO pada klien Anda dengan semua bidang yang mungkin bisa masuk. Dalam kasus API back-end yang mungkin dibagikan di antara proyek, ini bisa bermasalah jika bidang tambahan ditambahkan. Pada dasarnya itu adalah daftar putih vs daftar hitam bidang.
theblang
11
Pendekatan ini tidak berfungsi untuk saya, karena tidak hanya mengecualikan bidang dari serialisasi gson, tetapi mengecualikan bidang dari serialisasi aplikasi internal (menggunakan antarmuka Serializable).
pkk
8
sementara mencegah SERIALISASI dan DESERIALISASI suatu bidang. Ini tidak sesuai dengan kebutuhan saya.
Loenix
318

Nishant memberikan solusi yang baik, tetapi ada cara yang lebih mudah. Cukup tandai bidang yang diinginkan dengan anotasi @Ekspose, seperti:

@Expose private Long id;

Tinggalkan bidang apa pun yang tidak ingin Anda serialkan. Kemudian buat saja objek Gson Anda dengan cara ini:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
Jayea
sumber
95
Apakah mungkin untuk memiliki sesuatu seperti "notExpose" dan hanya mengabaikan yang untuk kasus di mana semua kecuali satu bidang harus bersambung dan menambahkan anotasi ke semuanya adalah mubazir?
Daniil Shevelev
2
@ David Saya baru-baru ini memiliki skenario seperti itu. Sangat mudah untuk menulis Strategi Pengecualian khusus yang melakukan hal itu. Lihat jawaban Nishant. Satu-satunya masalah adalah memasukkan sekelompok kelas kontainer dan bermain-main dengan skipclass vs skipfield (bidang dapat berupa kelas ...)
keyser
1
@Ash Jawaban saya di bawah melakukan hal itu.
Derek Shockey
Solusi yang hebat. Saya mengalami skenario di mana saya ingin bidang serial ke disk tetapi diabaikan ketika mengirimnya ke server melalui gson. Sempurna terima kasih!
Slynk
1
@Danlil Anda harus dapat menggunakan @Expose (serialize = false, deserialize = false)
Hrk
238

Jadi, Anda ingin mengecualikan firstName dan country.name. Di sini Anda ExclusionStrategyakan terlihat seperti apa

    public class TestExclStrat implements ExclusionStrategy {

        public boolean shouldSkipClass(Class<?> arg0) {
            return false;
        }

        public boolean shouldSkipField(FieldAttributes f) {

            return (f.getDeclaringClass() == Student.class && f.getName().equals("firstName"))||
            (f.getDeclaringClass() == Country.class && f.getName().equals("name"));
        }

    }

Jika Anda melihat dengan seksama itu mengembalikan trueuntuk Student.firstNamedan Country.name, yang ingin Anda kecualikan.

Anda perlu menerapkan ini ExclusionStrategyseperti ini,

    Gson gson = new GsonBuilder()
        .setExclusionStrategies(new TestExclStrat())
        //.serializeNulls() <-- uncomment to serialize NULL fields as well
        .create();
    Student src = new Student();
    String json = gson.toJson(src);
    System.out.println(json);

Ini mengembalikan:

{ "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91}}

Saya berasumsi objek negara diinisialisasi dengan id = 91Ldi kelas siswa.


Anda mungkin suka. Misalnya, Anda tidak ingin membuat serial bidang apa pun yang berisi string "nama" dalam namanya. Melakukan hal ini:

public boolean shouldSkipField(FieldAttributes f) {
    return f.getName().toLowerCase().contains("name"); 
}

Ini akan mengembalikan:

{ "initials": "P.F", "country": { "id": 91 }}

EDIT: Menambahkan lebih banyak informasi seperti yang diminta.

Ini ExclusionStrategyakan melakukan hal itu, tetapi Anda harus memberikan "Nama Bidang yang Sepenuhnya Memenuhi Syarat". Lihat di bawah:

    public class TestExclStrat implements ExclusionStrategy {

        private Class<?> c;
        private String fieldName;
        public TestExclStrat(String fqfn) throws SecurityException, NoSuchFieldException, ClassNotFoundException
        {
            this.c = Class.forName(fqfn.substring(0, fqfn.lastIndexOf(".")));
            this.fieldName = fqfn.substring(fqfn.lastIndexOf(".")+1);
        }
        public boolean shouldSkipClass(Class<?> arg0) {
            return false;
        }

        public boolean shouldSkipField(FieldAttributes f) {

            return (f.getDeclaringClass() == c && f.getName().equals(fieldName));
        }

    }

Inilah cara kita dapat menggunakannya secara umum.

    Gson gson = new GsonBuilder()
        .setExclusionStrategies(new TestExclStrat("in.naishe.test.Country.name"))
        //.serializeNulls()
        .create();
    Student src = new Student();
    String json = gson.toJson(src);
    System.out.println(json); 

Ia mengembalikan:

{ "firstName": "Philip" , "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91 }}
Nishant
sumber
Terima kasih atas jawabannya. Yang saya inginkan adalah membuat ExclusionStrategy yang dapat mengambil string seperti country.namedan hanya mengecualikan bidang namesaat membuat serial bidang country. Itu harus cukup umum untuk diterapkan pada setiap kelas yang memiliki properti bernama negara dari kelas Country. Saya tidak ingin membuat Strategi Pengecualian untuk setiap kelas
Liviu T.
@Liviu T. Saya telah memperbarui jawabannya. Itu membutuhkan pendekatan generik. Anda mungkin menjadi lebih kreatif, tapi saya tetap elemen.
Nishant
Ty untuk pembaruan. Apa yang saya coba lihat apakah mungkin untuk mengetahui di mana dalam grafik objek saya ketika metode itu disebut sehingga saya dapat mengecualikan beberapa bidang negara tetapi tidak countryOfBirth (misalnya) kelas yang sama tetapi properti yang berbeda. Saya telah mengedit pertanyaan saya untuk mengklarifikasi apa yang saya coba capai
Liviu T.
Apakah ada cara untuk mengecualikan bidang yang memiliki nilai kosong?
Yusuf K.
12
Jawaban ini harus ditandai sebagai jawaban yang lebih disukai. Berbeda dengan jawaban lain yang saat ini memiliki lebih banyak suara, solusi ini tidak mengharuskan Anda untuk memodifikasi kelas kacang. Ini merupakan nilai tambah yang besar. Bagaimana jika orang lain menggunakan kelas kacang yang sama, dan Anda menandai bidang yang mereka inginkan bertahan sebagai "sementara"?
user64141
221

Setelah membaca semua jawaban yang tersedia saya temukan, yang paling fleksibel, dalam kasus saya, adalah menggunakan @Excludeanotasi khusus . Jadi, saya menerapkan strategi sederhana untuk ini (saya tidak ingin menandai semua bidang menggunakan @Exposeatau saya ingin menggunakan transientyang bertentangan dengan dalam Serializableserialisasi aplikasi ):

Anotasi:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {
}

Strategi:

public class AnnotationExclusionStrategy implements ExclusionStrategy {

    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getAnnotation(Exclude.class) != null;
    }

    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return false;
    }
}

Pemakaian:

new GsonBuilder().setExclusionStrategies(new AnnotationExclusionStrategy()).create();
pkk
sumber
16
Sebagai catatan tambahan, jika Anda ingin menggunakan strategi pengecualian Anda hanya untuk serialisasi atau deserialisasi, gunakan: addSerializationExclusionStrategyatau addDeserializationExclusionStrategybukannyasetExclusionStrategies
GLee
9
Sempurna! Solusi sementara tidak bekerja untuk saya karena saya menggunakan Realm untuk DB dan saya ingin mengecualikan bidang hanya dari Gson, tetapi tidak Realm (yang sementara tidak)
Marcio Granzotto
3
Ini harus menjadi jawaban yang diterima. Untuk mengabaikan bidang nol, ubah saja f.getAnnotation(Exclude.class) != nullkef.getAnnotation(Exclude.class) == null
Sharp Edge
3
Sangat bagus ketika Anda tidak dapat menggunakan transientkarena kebutuhan perpustakaan lain. Terima kasih!
Martin D
1
Juga bagus untuk saya karena Android membuat serial kelas saya di antara kegiatan, tapi saya hanya ingin mereka dikecualikan ketika saya menggunakan GSON. Ini membuat aplikasi saya tetap berfungsi dengan cara yang sama sampai ia ingin membungkusnya untuk dikirim ke orang lain.
ThePartyTurtle
81

Saya mengalami masalah ini, di mana saya memiliki sejumlah kecil bidang yang ingin saya kecualikan hanya dari serialisasi, jadi saya mengembangkan solusi yang cukup sederhana yang menggunakan @Exposeanotasi Gson dengan strategi pengecualian kustom.

Satu-satunya cara built-in untuk digunakan @Exposeadalah dengan mengatur GsonBuilder.excludeFieldsWithoutExposeAnnotation(), tetapi seperti namanya, bidang tanpa eksplisit @Exposediabaikan. Karena saya hanya memiliki beberapa bidang yang ingin saya kecualikan, saya merasa prospek menambahkan anotasi ke setiap bidang sangat rumit.

Saya secara efektif menginginkan kebalikannya, di mana semuanya dimasukkan kecuali saya secara eksplisit digunakan @Exposeuntuk mengecualikannya. Saya menggunakan strategi pengecualian berikut untuk mencapai ini:

new GsonBuilder()
        .addSerializationExclusionStrategy(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                return expose != null && !expose.serialize();
            }

            @Override
            public boolean shouldSkipClass(Class<?> aClass) {
                return false;
            }
        })
        .addDeserializationExclusionStrategy(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                return expose != null && !expose.deserialize();
            }

            @Override
            public boolean shouldSkipClass(Class<?> aClass) {
                return false;
            }
        })
        .create();

Sekarang saya dapat dengan mudah mengecualikan beberapa bidang dengan @Expose(serialize = false)atau @Expose(deserialize = false)anotasi (perhatikan bahwa nilai default untuk kedua @Exposeatribut adalah true). Anda tentu saja dapat menggunakan @Expose(serialize = false, deserialize = false), tetapi itu lebih ringkas dilakukan dengan mendeklarasikan bidang transientsebagai gantinya (yang masih berlaku dengan strategi pengecualian khusus ini).

Derek Shockey
sumber
Untuk efisiensi, saya dapat melihat kasus untuk menggunakan @ Expose (serialize = false, deserialize = false) daripada sementara.
paiego
1
@ Paiego Bisakah Anda memperluas itu? Saya sekarang bertahun-tahun dihapus dari menggunakan Gson, dan saya tidak mengerti mengapa anotasi lebih efisien daripada menandainya sementara.
Derek Shockey
Ahh, aku membuat kesalahan, terima kasih sudah menangkap ini. Saya mengira volatile untuk sementara. (mis. Tidak ada caching dan karena itu tidak ada masalah cache-koherensi dengan volatile, tetapi performanya kurang) Pokoknya, kode Anda bekerja dengan baik!
paiego
18

Anda dapat menjelajahi pohon json dengan gson.

Coba sesuatu seperti ini:

gson.toJsonTree(student).getAsJsonObject()
.get("country").getAsJsonObject().remove("name");

Anda juga dapat menambahkan beberapa properti:

gson.toJsonTree(student).getAsJsonObject().addProperty("isGoodStudent", false);

Diuji dengan gson 2.2.4.

Klaudia Rzewnicka
sumber
3
Saya bertanya-tanya apakah ini terlalu banyak hit kinerja jika Anda ingin menyingkirkan properti kompleks yang harus diurai sebelum dihapus. Pikiran?
Ben
Jelas bukan solusi yang skalabel, bayangkan semua sakit kepala yang harus Anda lalui jika Anda mengubah struktur objek Anda atau menambah / menghapus barang.
codenamezero
16

Saya datang dengan pabrik kelas untuk mendukung fungsi ini. Lulus kombinasi apa pun dari bidang atau kelas yang ingin Anda kecualikan.

public class GsonFactory {

    public static Gson build(final List<String> fieldExclusions, final List<Class<?>> classExclusions) {
        GsonBuilder b = new GsonBuilder();
        b.addSerializationExclusionStrategy(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes f) {
                return fieldExclusions == null ? false : fieldExclusions.contains(f.getName());
            }

            @Override
            public boolean shouldSkipClass(Class<?> clazz) {
                return classExclusions == null ? false : classExclusions.contains(clazz);
            }
        });
        return b.create();

    }
}

Untuk menggunakan, buat dua daftar (masing-masing adalah opsional), dan buat objek GSON Anda:

static {
 List<String> fieldExclusions = new ArrayList<String>();
 fieldExclusions.add("id");
 fieldExclusions.add("provider");
 fieldExclusions.add("products");

 List<Class<?>> classExclusions = new ArrayList<Class<?>>();
 classExclusions.add(Product.class);
 GSON = GsonFactory.build(null, classExclusions);
}

private static final Gson GSON;

public String getSomeJson(){
    List<Provider> list = getEntitiesFromDatabase();
    return GSON.toJson(list);
}
Domenic D.
sumber
Tentu saja, ini dapat dimodifikasi untuk melihat nama atribut yang sepenuhnya memenuhi syarat dan mengecualikannya pada pertandingan ...
Domenic D.
Saya lakukan contoh di bawah ini. Ini tidak bekerja. Tolong sarankan private GON GSON final statis; static {List <String> fieldExclusions = ArrayList baru <String> (); fieldExclusions.add ("id"); GSON = GsonFactory.build (fieldExclusions, null); } private static String getSomeJson () {String jsonStr = "[{\" id \ ": 111, \" name \ ": \" praveen \ ", \" age \ ": 16}, {\" id \ ": 222, \ "name \": \ "prashant \", \ "age \": 20}] "; kembalikan jsonStr; } public static static batal (String [] args) {String jsonStr = getSomeJson (); System.out.println (GSON.toJson (jsonStr)); }
Praveen Hiremath
13

Saya memecahkan masalah ini dengan anotasi khusus. Ini adalah kelas Anotasi "SkipSerialisation" saya:

@Target (ElementType.FIELD)
public @interface SkipSerialisation {

}

dan ini GsonBuilder saya:

gsonBuilder.addSerializationExclusionStrategy(new ExclusionStrategy() {

  @Override public boolean shouldSkipField (FieldAttributes f) {

    return f.getAnnotation(SkipSerialisation.class) != null;

  }

  @Override public boolean shouldSkipClass (Class<?> clazz) {

    return false;
  }
});

Contoh:

public class User implements Serializable {

  public String firstName;

  public String lastName;

  @SkipSerialisation
  public String email;
}
Hibbem
sumber
5
Gson: Cara mengecualikan bidang tertentu dari Serialisasi tanpa penjelasan
Ben
3
Anda juga harus menambahkan @Retention(RetentionPolicy.RUNTIME)anotasi Anda.
David Novák
9

Atau dapat mengatakan bidang apa yang tidak akan dibuka dengan:

Gson gson = gsonBuilder.excludeFieldsWithModifiers(Modifier.TRANSIENT).create();

di kelas Anda pada atribut:

private **transient** boolean nameAttribute;
lucasddaniel
sumber
17
Bidang transien dan statis dikecualikan secara default; tidak perlu menelepon excludeFieldsWithModifiers()untuk itu.
Derek Shockey
9

Saya menggunakan strategi ini: saya mengecualikan setiap bidang yang tidak ditandai dengan penjelasan @SerializedName , yaitu:

public class Dummy {

    @SerializedName("VisibleValue")
    final String visibleValue;
    final String hiddenValue;

    public Dummy(String visibleValue, String hiddenValue) {
        this.visibleValue = visibleValue;
        this.hiddenValue = hiddenValue;
    }
}


public class SerializedNameOnlyStrategy implements ExclusionStrategy {

    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getAnnotation(SerializedName.class) == null;
    }

    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return false;
    }
}


Gson gson = new GsonBuilder()
                .setExclusionStrategies(new SerializedNameOnlyStrategy())
                .create();

Dummy dummy = new Dummy("I will see this","I will not see this");
String json = gson.toJson(dummy);

Ia kembali

{"VisibleValue": "Saya akan melihat ini"}

MadMurdok
sumber
6

Pendekatan lain (terutama berguna jika Anda perlu membuat keputusan untuk mengecualikan bidang saat runtime) adalah mendaftarkan TypeAdapter dengan instance gson Anda. Contoh di bawah ini:

Gson gson = new GsonBuilder()
.registerTypeAdapter(BloodPressurePost.class, new BloodPressurePostSerializer())

Dalam kasus di bawah ini, server akan mengharapkan salah satu dari dua nilai tetapi karena keduanya ints maka gson akan membuat serial keduanya. Tujuan saya adalah untuk menghilangkan nilai apa pun yang nol (atau kurang) dari json yang diposting ke server.

public class BloodPressurePostSerializer implements JsonSerializer<BloodPressurePost> {

    @Override
    public JsonElement serialize(BloodPressurePost src, Type typeOfSrc, JsonSerializationContext context) {
        final JsonObject jsonObject = new JsonObject();

        if (src.systolic > 0) {
            jsonObject.addProperty("systolic", src.systolic);
        }

        if (src.diastolic > 0) {
            jsonObject.addProperty("diastolic", src.diastolic);
        }

        jsonObject.addProperty("units", src.units);

        return jsonObject;
    }
}
Damian
sumber
6

@TransientAnotasi Kotlin juga tampaknya berhasil.

data class Json(
    @field:SerializedName("serialized_field_1") val field1: String,
    @field:SerializedName("serialized_field_2") val field2: String,
    @Transient val field3: String
)

Keluaran:

{"serialized_field_1":"VALUE1","serialized_field_2":"VALUE2"}
lookashc
sumber
1

Saya bekerja hanya dengan meletakkan @Exposeanotasi, di sini versi saya yang saya gunakan

compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'

Di Modelkelas:

@Expose
int number;

public class AdapterRestApi {

Di Adapterkelas:

public EndPointsApi connectRestApi() {
    OkHttpClient client = new OkHttpClient.Builder()
            .connectTimeout(90000, TimeUnit.SECONDS)
            .readTimeout(90000,TimeUnit.SECONDS).build();

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(ConstantRestApi.ROOT_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .build();

    return retrofit.create  (EndPointsApi.class);
}
devitoper
sumber
1

Saya memiliki versi Kotlin

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
internal annotation class JsonSkip

class SkipFieldsStrategy : ExclusionStrategy {

    override fun shouldSkipClass(clazz: Class<*>): Boolean {
        return false
    }

    override fun shouldSkipField(f: FieldAttributes): Boolean {
        return f.getAnnotation(JsonSkip::class.java) != null
    }
}

dan bagaimana Anda dapat menambahkan ini ke Retrofit GSONConverterFactory:

val gson = GsonBuilder()
                .setExclusionStrategies(SkipFieldsStrategy())
                //.serializeNulls()
                //.setDateFormat(DateFormat.LONG)
                //.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
                //.setPrettyPrinting()
                //.registerTypeAdapter(Id.class, IdTypeAdapter())
                .create()
        return GsonConverterFactory.create(gson)
Michał Ziobro
sumber
0

Inilah yang selalu saya gunakan:

Perilaku default yang diterapkan di Gson adalah bahwa bidang objek nol diabaikan.

Berarti objek Gson tidak membuat serialisasi bidang dengan nilai nol ke JSON. Jika bidang dalam objek Java adalah null, Gson mengecualikannya.

Anda dapat menggunakan fungsi ini untuk mengubah beberapa objek menjadi nol atau diatur dengan baik oleh Anda sendiri

     /**
   * convert object to json
   */
  public String toJson(Object obj) {
    // Convert emtpy string and objects to null so we don't serialze them
    setEmtpyStringsAndObjectsToNull(obj);
    return gson.toJson(obj);
  }

  /**
   * Sets all empty strings and objects (all fields null) including sets to null.
   *
   * @param obj any object
   */
  public void setEmtpyStringsAndObjectsToNull(Object obj) {
    for (Field field : obj.getClass().getDeclaredFields()) {
      field.setAccessible(true);
      try {
        Object fieldObj = field.get(obj);
        if (fieldObj != null) {
          Class fieldType = field.getType();
          if (fieldType.isAssignableFrom(String.class)) {
            if(fieldObj.equals("")) {
              field.set(obj, null);
            }
          } else if (fieldType.isAssignableFrom(Set.class)) {
            for (Object item : (Set) fieldObj) {
              setEmtpyStringsAndObjectsToNull(item);
            }
            boolean setFielToNull = true;
            for (Object item : (Set) field.get(obj)) {
              if(item != null) {
                setFielToNull = false;
                break;
              }
            }
            if(setFielToNull) {
              setFieldToNull(obj, field);
            }
          } else if (!isPrimitiveOrWrapper(fieldType)) {
            setEmtpyStringsAndObjectsToNull(fieldObj);
            boolean setFielToNull = true;
            for (Field f : fieldObj.getClass().getDeclaredFields()) {
              f.setAccessible(true);
              if(f.get(fieldObj) != null) {
                setFielToNull = false;
                break;
              }
            }
            if(setFielToNull) {
              setFieldToNull(obj, field);
            }
          }
        }
      } catch (IllegalAccessException e) {
        System.err.println("Error while setting empty string or object to null: " + e.getMessage());
      }
    }
  }

  private void setFieldToNull(Object obj, Field field) throws IllegalAccessException {
    if(!Modifier.isFinal(field.getModifiers())) {
      field.set(obj, null);
    }
  }

  private boolean isPrimitiveOrWrapper(Class fieldType)  {
    return fieldType.isPrimitive()
        || fieldType.isAssignableFrom(Integer.class)
        || fieldType.isAssignableFrom(Boolean.class)
        || fieldType.isAssignableFrom(Byte.class)
        || fieldType.isAssignableFrom(Character.class)
        || fieldType.isAssignableFrom(Float.class)
        || fieldType.isAssignableFrom(Long.class)
        || fieldType.isAssignableFrom(Double.class)
        || fieldType.isAssignableFrom(Short.class);
  }
Julian Solarte
sumber