1. Overview
In this short tutorial, we’re going to explore the available options for excluding one or more fields of a Java class and its subclasses from Gson serialization.
2. Initial Setup
Let’s first define our classes:
@Data
@AllArgsConstructor
public class MyClass {
private long id;
private String name;
private String other;
private MySubClass subclass;
}
@Data
@AllArgsConstructor
public class MySubClass {
private long id;
private String description;
private String otherVerboseInfo;
}
We’ve annotated them with Lombok for convenience (syntactic sugar for getters, setters, constructors…).
Let’s now populate them:
MySubClass subclass = new MySubClass(42L, "the answer", "Verbose field not to serialize")
MyClass source = new MyClass(1L, "foo", "bar", subclass);
Our goal is to prevent the MyClass.other and MySubClass.otherVerboseInfo fields from being serialized.
The output we are expecting to get is:
{
"id":1,
"name":"foo",
"subclass":{
"id":42,
"description":"the answer"
}
}
In Java:
String expectedResult = "{\"id\":1,\"name\":\"foo\",\"subclass\":{\"id\":42,\"description\":\"the answer\"}}";
3. Transient Modifier
We can mark a field with the transient modifier:
public class MyClass {
private long id;
private String name;
private transient String other;
private MySubClass subclass;
}
public class MySubClass {
private long id;
private String description;
private transient String otherVerboseInfo;
}
Gson serializer will ignore every field declared as transient:
String jsonString = new Gson().toJson(source);
assertEquals(expectedResult, jsonString);
While this is very fast, it also comes with a severe downside: every serialization tool will take transient into account, not only Gson.
Transient is the Java way to exclude from serialization, then our field will also be filtered out by Serializable‘s serialization, and by every library tool or framework managing our objects.
Additionally, the transient keyword always works for both serialization and deserialization, which can be limiting depending on the use-cases.
4. @Expose Annotation
Gson com.google.gson.annotations @Expose annotation works the other way around.
We can use it to declare which fields to serialize, and ignore the others:
public class MyClass {
@Expose
private long id;
@Expose
private String name;
private String other;
@Expose
private MySubClass subclass;
}
public class MySubClass {
@Expose
private long id;
@Expose
private String description;
private String otherVerboseInfo;
}
For this, we need to instantiate Gson with a GsonBuilder:
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
String jsonString = gson.toJson(source);
assertEquals(expectedResult, jsonString);
This time we can control at field level whether the filtering should happen for serialization, deserialization, or both (default).
Let’s see how to prevent MyClass.other from being serialized, but allowing it to be populated during a deserialization from JSON:
@Expose(serialize = false, deserialize = true)
private String other;
While this is the easiest way provided by Gson, and it doesn’t affect other libraries, it could imply redundancy in the code. If we have a class with a hundred of fields, and we only want to exclude one field, we need to write ninety-nine annotation, which is overkill.
5. ExclusionStrategy
A highly customizable solution is the usage of a com.google.gson.ExclusionStrategy.
It allows us to define (externally or with an Anonimous Inner Class) a strategy to instruct the GsonBuilder whether to serialize fields (and/or classes) with custom criteria.
Gson gson = new GsonBuilder()
.addSerializationExclusionStrategy(strategy)
.create();
String jsonString = gson.toJson(source);
assertEquals(expectedResult, jsonString);
Let’s see some examples of smart strategies to use.
5.1. With Classes and Fields Names
Of course, we can also hardcode one or more fields/classes names:
ExclusionStrategy strategy = new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes field) {
if (field.getDeclaringClass() == MyClass.class && field.getName().equals("other")) {
return true;
}
if (field.getDeclaringClass() == MySubClass.class && field.getName().equals("otherVerboseInfo")) {
return true;
}
return false;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
};
This is fast and straight to the point, but not very reusable and also prone to errors in case we rename our attributes.
5.2. With Business Criteria
Since we simply have to return a boolean, we can implement every business logic we like inside that method.
In the following example, we’ll identify every field starting with “other” as fields which shouldn’t be serialized, no matter the class they belong:
ExclusionStrategy strategy = new ExclusionStrategy() {
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
@Override
public boolean shouldSkipField(FieldAttributes field) {
return field.getName().startsWith("other");
}
};
5.3. With a Custom Annotation
Another smart approach is to create a custom annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {}
We can then exploit ExclusionStrategy in order to make it work exactly as with the @Expose annotation, but inversely:
public class MyClass {
private long id;
private String name;
@Exclude
private String other;
private MySubClass subclass;
}
public class MySubClass {
private long id;
private String description;
@Exclude
private String otherVerboseInfo;
}
And here is the strategy:
ExclusionStrategy strategy = new ExclusionStrategy() {
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
@Override
public boolean shouldSkipField(FieldAttributes field) {
return field.getAnnotation(Exclude.class) != null;
}
};
This StackOverflow answer firstly described this technique.
It allows us to write the annotation and the Strategy once, and to dynamically annotate our fields without further modification.
5.4. Extend Exclusion Strategy to Deserialization
No matter which strategy we’ll use, we can always control where it should be applied.
Only during serialization:
Gson gson = new GsonBuilder().addSerializationExclusionStrategy(strategy)
Only during deserialization:
Gson gson = new GsonBuilder().addDeserializationExclusionStrategy(strategy)
Always:
Gson gson = new GsonBuilder().setExclusionStrategies(strategy);
6. Conclusion
We’ve seen different ways to exclude fields from a class and its subclasses during Gson serialization.
We’ve also explored the main advantages and pitfalls of every solution.
As always, the full source code is available over on Github.