NullDefense | Gson
Removes invalid objects during Gson parsing which are marked as required, yet null/empty.
Code Quality
Installation
In build.gradle
add the following dependencies
dependencies {
compile 'com.venomvendor:gson-nulldefense:<latest.version>'
}
Usage Guide
Annotation
- Use any existing annotation with RetentionPolicy.RUNTIME
-
Creating simple annotation with RetentionPolicy.RUNTIME
package com.example.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /* Retention Policy should be runtime */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface Mandatory { }
Tip: If you are using Realm in Android, use @io.realm.annotations.Required
Marking required fields
- Annotate in POJOs used with Gson
public class CustomerDetail { @Mandatory @SerializedName("first_name") private String firstName; // Optional @SerializedName("lastName") private String lastName; @Mandatory @SerializedName("addresses") private List<Address> addresses; ... ... ... // Setter/Getter }
Registering Adapter
TypeAdapterFactory nullDefenceAdapter = new NullDefenseTypeAdapterFactory(Mandatory.class);
Gson gson = new GsonBuilder()
// Add null defense Adapter
.registerTypeAdapterFactory(nullDefenceAdapter)
// More options
.enableComplexMapKeySerialization()
.setPrettyPrinting()
.setLenient()
.serializeNulls()
.create();
To Retain Empty Collection
TypeAdapterFactory typeAdapter = new NullDefenseTypeAdapterFactory(Mandatory.class)
// To retain empty collection
.retainEmptyCollection();
To Remove Empty Collection
TypeAdapterFactory typeAdapter = new NullDefenseTypeAdapterFactory(Mandatory.class)
// To remove empty collection, this is default
.removeEmptyCollection();
Using with Retrofit⬈
TypeAdapterFactory nullDefenceAdapter = new NullDefenseTypeAdapterFactory(Mandatory.class);
Gson gson = new GsonBuilder()
// Add null defense Adapter
.registerTypeAdapterFactory(nullDefenceAdapter)
// More options
. . .
.create();
Retrofit retrofit = new Retrofit.Builder()
// Pass custom gson
.addConverterFactory(GsonConverterFactory.create(gson))
// More options
. . .
.build();
Tests
POJO
public class Parent {
@Mandatory
private String name;
@Mandatory
private CustomList<Child> children;
// Setter/Getter
}
public class Child {
@Mandatory
private String name;
// I am optional
private Boolean isMale;
// Has no effect on primitive
@Mandatory
private int age;
@Mandatory
private Language language;
// Setter/Getter
}
public class Language {
@Mandatory
private List<String> knownLanguages;
// I am optional
private List<String> learning;
// Setter/Getter
}
parent-valid.json
{
"name": "VenomVendor",
"children": [
{
"name": "Queen",
"isMale": false,
"age": 26,
"language": {
"knownLanguages": [
"Telugu",
"Tamil"
],
"learning": [
"French"
]
}
},
{
"name": "Prince",
"isMale": true,
"age": 5,
"language": {
"knownLanguages": [
"English",
"Sanskrit"
],
"learning": []
}
},
{
"name": "Princess",
"isMale": false,
"age": 3,
"language": {
"knownLanguages": [
"Kannada"
],
"learning": null
}
}
]
}
Result
assertEquals(3, parent.getChildren().size());
valid-missing-primitive.json
Annotation has no effect on primitive fields
{
"name": "VenomVendor",
"children": [
{
"name": "Queen",
"isMale": false,
"age": 26,
"language": {
"knownLanguages": [
"Telugu",
"Tamil"
],
"learning": [
"French"
]
}
},
{
"name": "Prince",
"isMale": true,
"age": null,
"language": {
"knownLanguages": [
"English",
"Sanskrit"
],
"learning": []
}
},
{
"name": "Princess",
"isMale": false,
"language": {
"knownLanguages": [
"Kannada"
],
"learning": null
}
}
]
}
Result
assertEquals(3, parent.getChildren().size());
missing-name-in-child.json
Should generate one child, as rest doesn’t have mandatory
name
{
"name": "VenomVendor",
"children": [
{
/* Missing Mandatory Field */
"isMale": false,
"age": 26,
"language": {
"knownLanguages": [
"Telugu",
"Tamil"
],
"learning": [
"French"
]
}
},
{
"name": null, // Null Mandatory Field
"isMale": true,
"age": 5,
"language": {
"knownLanguages": [
"English",
"Sanskrit"
],
"learning": []
}
},
{
"name": "Princess",
"isMale": false,
"age": 3,
"language": {
"knownLanguages": [
"Kannada"
],
"learning": null
}
}
]
}
Result
assertEquals(1, parent.getChildren().size());
missing-known-language-no-child.json
Should not generate any children due to missing
knownLanguages
, hence parent is alsonull
, as it doesn’t have mandatory children
{
"name": "VenomVendor",
"children": [
{
"name": "Queen",
"isMale": false,
"age": 26,
"language": {
"knownLanguages": null,
"learning": [
"French"
]
}
},
{
"name": "Prince",
"isMale": true,
"age": 5,
"language": {
"knownLanguages": [],
"learning": []
}
},
{
"name": "Princess",
"isMale": false,
"age": 3,
"language": {
"learning": null
}
}
]
}
Result
assertNull(parent);