452

I'm trying to learn Gson and I'm struggling with field exclusion. Here are my classes

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

I can use the GsonBuilder and add an ExclusionStrategy for a field name like firstName or country but I can't seem to manage to exclude properties of certain fields like country.name.

Using the method public boolean shouldSkipField(FieldAttributes fa), FieldAttributes doesn't contain enough information to match the field with a filter like country.name.

P.S: I want to avoid annotations since I want to improve on this and use RegEx to filter fields out.

Edit: I'm trying to see if it's possible to emulate the behavior of Struts2 JSON plugin

using Gson

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

Edit: I reopened the question with the following addition:

I added a second field with the same type to futher clarify this problem. Basically I want to exclude country.name but not countrOfBirth.name. I also don't want to exclude Country as a type. So the types are the same it's the actual place in the object graph that I want to pinpoint and exclude.

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
Liviu T.
  • 23,584
  • 10
  • 62
  • 58
  • 1
    Still as of version 2.2 I still can't specify a path to field to exclude. http://flexjson.sourceforge.net/ feels like a good alternative. – Liviu T. Feb 08 '13 at 21:50
  • Have a look on [my answer](https://stackoverflow.com/a/47243035/6413377) to a quite a similar question. It is based on creating a custom `JsonSerializer` for some type -`Country` in your case- for which then is applied an `ExclusionStrategy` which decides what fields to serialize. – pirho Dec 24 '17 at 00:02
  • https://stackoverflow.com/questions/3544919/what-are-transient-and-volatile-modifiers – Pangamma Jan 29 '19 at 05:34

17 Answers17

669

Any fields you don't want serialized in general you should use the "transient" modifier, and this also applies to json serializers (at least it does to a few that I have used, including gson).

If you don't want name to show up in the serialized json give it a transient keyword, eg:

private transient String name;

More details in the Gson documentation

dimo414
  • 47,227
  • 18
  • 148
  • 244
Chris Seline
  • 6,943
  • 1
  • 16
  • 14
  • 7
    it's almost the same thing as an exclusion annotation as in it applies to all instances of that class. I wanted runtime dynamic exclusion. I some cases I want some fields excluded in order to provide a lighter/restricted response and in others I want the full object serialized – Liviu T. May 07 '11 at 07:36
  • 37
    One thing to note is that transient effects both serialization and deserialization. It will also emit the value from been serialized into the object, even if it is present in the JSON. – Kong Apr 16 '13 at 03:48
  • Great way of exclusion. It helps me to generate JSON configs from POJOs to use with TickTick. – Viacheslav Dobromyslov Oct 05 '14 at 03:45
  • 3
    The problem with using `transient` instead of `@Expose` is that you still have to mock up a POJO on your client with all fields that could possibly come in. In the case of a back-end API that might be shared among projects, this could be problematic in case additional fields are added. Essentially it is whitelisting vs blacklisting of the fields. – theblang Nov 25 '14 at 17:08
  • 12
    This approach didn't work for me, as it not only excluded the field from gson serialization, but excluded the field from internal app serialization (using Serializable interface). – pkk Jan 16 '15 at 14:52
  • 1
    This is a very dangerous approach: as @pkk said, `transient` not only excludes the field from the serialization, but also doesn't let JPA to treat the field (it will not be automatically managed). – Enkk Feb 18 '15 at 14:49
  • You might want to try alternative solution presented in my answer using custom "exclusion" annotation, that works "like" transient modifier, but is limited to Gson parser scope. – pkk Feb 19 '15 at 10:02
  • 8
    transient prevents SERIALIZATION and DESERIALIZATION of a field. This does not match my needs. – Loenix Jul 20 '15 at 08:18
  • 3
    It does not work when the field must be persisted in the database. For example, hibernate would ignore transient fields too. – Jordan Silva Nov 30 '15 at 12:58
  • 2
    How about if this field is to be saved in database? This solution will break – F.O.O Mar 16 '16 at 22:42
  • 1
    transient, final, static fields are Not allowed from >v0.88 – Jemshit Jun 30 '16 at 07:38
  • This should not be used as it restrict field to box and unbox from all functionality. You even can not send transient field in serializable/parcelable i.e. within activity or fragment. – Sandeep Jan 16 '18 at 12:14
  • transient has a specific semantics in Java. You shouldn't use it unless you want your field to really be transient. – Prof Mo Jan 26 '18 at 15:19
  • @ProfMo and others, the transient keyword is used to prevent data from being serialized/deserialized. Since converting an object to JSON is a form of serialization, I believe it is used properly. – Chris Seline Jan 31 '18 at 17:48
  • Why does Google always want to mess with your private parts? This is scary, I have a huge project (Android front end and JEE back end) that I used Gson in heavily. And there I was, thinking Gson would just ignore the private parts since JAXRS serializers do that by default. F***** hell man. Good thing it was over SSL. Now I have to refactor. – TheRealChx101 Feb 09 '18 at 01:45
  • use @Transient in Kotlin – BuffK Aug 12 '20 at 08:18
327

Nishant provided a good solution, but there's an easier way. Simply mark the desired fields with the @Expose annotation, such as:

@Expose private Long id;

Leave out any fields that you do not want to serialize. Then just create your Gson object this way:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
JayPea
  • 9,551
  • 7
  • 44
  • 65
  • 109
    Is it possible to have something like "notExpose" and only ignore those for the case where all but one field must be serialized and adding annotations to all of them is redundant? – Daniil Shevelev Feb 18 '14 at 00:19
  • 3
    @DaSh I recently had such a scenario. It was very easy to write a custom ExclusionStrategy which did exactly that. See Nishant's answer. The only problem was to include a bunch of container classes and fiddle with skipclass vs skipfield (fields can be classes...) – keyser Jul 14 '14 at 11:20
  • 2
    @DaSh [My answer](http://stackoverflow.com/a/17733569/657111) below does exactly that. – Derek Shockey Aug 26 '14 at 03:37
  • What a great solution. I was running into a scenario where I want a field serialized to disk but to be ignored when sending it to a server via gson. Perfect, thanks! – Slynk Sep 16 '15 at 15:15
  • 2
    @Danlil You should be able to use @Expose(serialize = false, deserialize = false) – Hrk Jun 13 '16 at 12:28
  • Can I somehow let GSON know to read a field, but not write it? I updated a classe's fields, so I want the "old" fields to be read but only the new ones should be written...? – jpm Sep 08 '16 at 10:24
  • @DaniilShevelev Thats exactly what i was also looking for and i implemented below. -------- public class DeserializationExclusionStrategy implements ExclusionStrategy{ @ Override public boolean shouldSkipField(FieldAttributes f) { Collection annotations = f.getAnnotations(); if (!CollectionUtils.isEmpty(annotations)) { for (Annotation annotation : annotations) { if (annotation instanceof Expose) { return true; } } } return false; } @ Override public boolean shouldSkipClass(Class> clazz) { return false; } } – Biscuit Coder May 24 '17 at 17:04
  • Q: `how to ... without annotations`. A: `use annotation...` http://i0.kym-cdn.com/entries/icons/mobile/000/010/277/genius.jpg – Vladyslav Matviienko Jan 29 '18 at 07:57
  • what it bad here is that you are forced to use `GsonBuilder` for this, so if you are in control of the code that serializes that's fine, but if someone else serializes a hierarchy of objects, without ever knowing that some field was not meant to be serialized and will use a plain `new Gson()...` – Eugene Nov 10 '18 at 22:53
250

So, you want to exclude firstName and country.name. Here is what your ExclusionStrategy should look like

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

    }

If you see closely it returns true for Student.firstName and Country.name, which is what you want to exclude.

You need to apply this ExclusionStrategy like this,

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

This returns:

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

I assume the country object is initialized with id = 91L in student class.


You may get fancy. For example, you do not want to serialize any field that contains "name" string in its name. Do this:

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

This will return:

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

EDIT: Added more info as requested.

This ExclusionStrategy will do the thing, but you need to pass "Fully Qualified Field Name". See below:

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

    }

Here is how we can use it generically.

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

It returns:

{ "firstName": "Philip" , "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91 }}
Community
  • 1
  • 1
Nishant
  • 54,584
  • 13
  • 112
  • 127
  • Thank you for you answer. What I want is to create an ExclusionStrategy that can take a string like `country.name` and only exlude the field `name` when serializing the field `country`. It should be generic enough to apply to every class that has a property named country of the Country class. I don't want to create an ExclusionStrategy for every class – Liviu T. Jan 26 '11 at 11:59
  • @Liviu T. I have updated the answer. That takes generic approach. You may get even more creative, but I kept it elemental. – Nishant Jan 26 '11 at 12:48
  • Ty for the update. What I;m trying to see if it's possible to know where in the object graph I am when the method it called so I can exclude some fields of country but not countryOfBirth(for example) so same class but different properties. I've edited my question to clarify what I'm trying to achieve – Liviu T. Jan 26 '11 at 15:08
  • Is there a way to exclude fields which has empty values? – Yusuf K. Jul 21 '11 at 11:48
  • I have the same question than @ykartal. How can I exclude empty fields? – VansFannel Jul 11 '13 at 09:48
  • you mean null value or empty string. Didn't get what do you mean by empty. – Nishant Jul 11 '13 at 10:05
  • 14
    This answer should be marked as the preferred answer. Unlike the other answers that currently have more votes, this solution does not require you to modify the bean class. This is a huge plus. What if someone else were using the same bean class, and you marked a field they wanted persisted as "transient"? – user64141 Jun 17 '15 at 00:18
  • The answer is ideal, but GSON is not. The shouldSkipField(FieldAttributes f) is called for root level fields only. It can be easily checked by simple println(). Even more, @Expose works for root level only, too. It seems, GSON us useless for more complex work. – Gangnus Aug 05 '16 at 11:17
  • @user64141 while you are right, this is still not a solution. Imagine that you are in control of the code that serializes something and you indeed use `GsonBuilder` for this; but now imagine the other side - someone serializing your Object *by accident* - it's a big hierarchy for example, and does not even know about your special treatment of some fields, so it uses a plain `new Gson()...`. I don't see a way to achieve this – Eugene Nov 10 '18 at 22:57
246

After reading all available answers I found out, that most flexible, in my case, was to use custom @Exclude annotation. So, I implemented simple strategy for this (I didn't want to mark all fields using @Expose nor I wanted to use transient which conflicted with in app Serializable serialization) :

Annotation:

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

Strategy:

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

Usage:

new GsonBuilder().setExclusionStrategies(new AnnotationExclusionStrategy()).create();
pkk
  • 3,691
  • 2
  • 21
  • 29
  • 20
    As an additional note, if you want to use your exclusion strategy for only serialization or only deserialization, use: `addSerializationExclusionStrategy` or `addDeserializationExclusionStrategy` instead of `setExclusionStrategies` – GLee Jul 16 '15 at 01:38
  • 9
    Perfect! The transient solution doesn't work for me because I'm using Realm for DB and I want to exclude a field only from Gson, but not Realm (wich transient does) – Marcio Granzotto Apr 15 '16 at 13:31
  • 3
    This should be the accepted answer. To ignore null fields just change `f.getAnnotation(Exclude.class) != null` to `f.getAnnotation(Exclude.class) == null` – Sharp Edge Aug 02 '16 at 16:52
  • 3
    Excellent when you cannot use `transient` because of other libraries' needs. Thanks! – Martin D Aug 31 '17 at 08:39
  • 1
    Also great for me because Android serializes my classes between activities, but I only want them excluded when I use GSON. This let's my app keep functioning the same way until it wants to wrap them up to send off to others. – ThePartyTurtle Oct 15 '18 at 18:16
  • any alternative for Moshi? – Francis Mar 21 '19 at 14:17
87

I ran into this issue, in which I had a small number of fields I wanted to exclude only from serialization, so I developed a fairly simple solution that uses Gson's @Expose annotation with custom exclusion strategies.

The only built-in way to use @Expose is by setting GsonBuilder.excludeFieldsWithoutExposeAnnotation(), but as the name indicates, fields without an explicit @Expose are ignored. As I only had a few fields I wanted to exclude, I found the prospect of adding the annotation to every field very cumbersome.

I effectively wanted the inverse, in which everything was included unless I explicitly used @Expose to exclude it. I used the following exclusion strategies to accomplish this:

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

Now I can easily exclude a few fields with @Expose(serialize = false) or @Expose(deserialize = false) annotations (note that the default value for both @Expose attributes is true). You can of course use @Expose(serialize = false, deserialize = false), but that is more concisely accomplished by declaring the field transient instead (which does still take effect with these custom exclusion strategies).

Derek Shockey
  • 1,837
  • 17
  • 15
  • For efficiency, I can see a case for using @Expose(serialize = false, deserialize = false) rather than transient. – paiego Aug 23 '18 at 18:12
  • 1
    @paiego Can you expand on that? I'm now many years removed from using Gson, and I don't understand why the annotation is more efficient than marking it transient. – Derek Shockey Aug 23 '18 at 22:22
  • Ahh, I made a mistake, thanks for catching this. I mistook volatile for transient. (e.g. There is no caching and therefore no cache-coherency problem with volatile, but it's less performant) Anyway, your code worked great! – paiego Sep 11 '18 at 20:48
18

You can explore the json tree with gson.

Try something like this :

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

You can add some properties also :

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

Tested with gson 2.2.4.

  • 3
    I wonder whether this is too much of a performance hit if you want to get rid of a complex property which has to be parsed before removal. Thoughts? – Ben Nov 08 '16 at 08:45
  • Definitely not a scalable solution, imagine all the headache you will need to go through if you change the structure of your object or add/remove stuff. – codenamezero Sep 21 '17 at 18:20
17

I came up with a class factory to support this functionality. Pass in any combination of either fields or classes you want to exclude.

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

    }
}

To use, create two lists (each is optional), and create your GSON object:

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.
  • 5,276
  • 4
  • 30
  • 41
  • Of course, this can be modified to look at the fully qualified name of the attribute and exclude that upon a match... – Domenic D. Apr 26 '12 at 18:07
  • I'm doing below example . This is not working. Pls suggest private static final Gson GSON; static { List fieldExclusions = new ArrayList(); 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}]"; return jsonStr; } public static void main(String[] args) { String jsonStr = getSomeJson(); System.out.println(GSON.toJson(jsonStr)); } – Praveen Hiremath Aug 30 '16 at 06:53
16

I solved this problem with custom annotations. This is my "SkipSerialisation" Annotation class:

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

}

and this is my GsonBuilder:

gsonBuilder.addSerializationExclusionStrategy(new ExclusionStrategy() {

  @Override public boolean shouldSkipField (FieldAttributes f) {

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

  }

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

    return false;
  }
});

Example :

public class User implements Serializable {

  public String firstName;

  public String lastName;

  @SkipSerialisation
  public String email;
}
Hibbem
  • 1,491
  • 15
  • 22
12

Kotlin's @Transientannotation also does the trick apparently.

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

Output:

{"serialized_field_1":"VALUE1","serialized_field_2":"VALUE2"}
lookashc
  • 879
  • 8
  • 17
9

Or can say whats fields not will expose with:

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

on your class on attribute:

private **transient** boolean nameAttribute;
lucasddaniel
  • 1,779
  • 22
  • 22
  • 19
    Transient and static fields are excluded by default; there's no need to call `excludeFieldsWithModifiers()` for that. – Derek Shockey Jul 18 '13 at 20:53
9

I used this strategy: i excluded every field which is not marked with @SerializedName annotation, i.e.:

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

It returns

{"VisibleValue":"I will see this"}

MadMurdok
  • 570
  • 6
  • 8
7

Another approach (especially useful if you need to make a decision to exclude a field at runtime) is to register a TypeAdapter with your gson instance. Example below:

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

In the case below, the server would expect one of two values but since they were both ints then gson would serialize them both. My goal was to omit any value that is zero (or less) from the json that is posted to the 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
  • 8,062
  • 4
  • 42
  • 43
2

in kotlin can use @Transient to ignore the field... eg.

data class MyClass{
@Transient var  myVar: Boolean
//....
}
j2emanue
  • 60,549
  • 65
  • 286
  • 456
1

I'm working just by putting the @Expose annotation, here my version that I use

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

In Model class:

@Expose
int number;

public class AdapterRestApi {

In the Adapter class:

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);
}
Yoh Deadfall
  • 2,711
  • 7
  • 28
  • 32
devitoper
  • 41
  • 3
1

I have Kotlin version

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

and how You can add this to 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
  • 10,759
  • 11
  • 88
  • 143
1

This what I always use:

The default behaviour implemented in Gson is that null object fields are ignored.

Means Gson object does not serialize fields with null values to JSON. If a field in a Java object is null, Gson excludes it.

You can use this function to convert some object to null or well set by your own

     /**
   * 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
  • 555
  • 6
  • 29
0

Use different DTO for cached object.

For example, you can create UserCached class and keep there only fields you need. After that, create mapper to map objects back & forth. Mapstruct is good for that.

Such approach solves the problem, decouples your application, and makes changes in your primary DTO more safe to make.