Gson: How to exclude specific fields from Serialization without annotations

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.


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


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

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

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

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