Remove empty collections from a JSON with Gson
Solution 1:
Steps to follow:
- Convert the JSON String into
Map<String,Object>
using Gson#fromJson() - Iterate the map and remove the entry from the map which are
null
or emptyArrayList
orMap
. - Form the JSON String back from the final map using Gson#toJson().
Note : Use GsonBuilder#setPrettyPrinting() that configures Gson to output Json that fits in a page for pretty printing.
Sample code:
import java.lang.reflect.Type;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
...
Type type = new TypeToken<Map<String, Object>>() {}.getType();
Map<String, Object> data = new Gson().fromJson(jsonString, type);
for (Iterator<Map.Entry<String, Object>> it = data.entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Object> entry = it.next();
if (entry.getValue() == null) {
it.remove();
} else if (entry.getValue().getClass().equals(ArrayList.class)) {
if (((ArrayList<?>) entry.getValue()).size() == 0) {
it.remove();
}
} else if (entry.getValue() instanceof Map){ //removes empty json objects {}
Map<?, ?> m = (Map<?, ?>)entry.getValue();
if(m.isEmpty()) {
it.remove();
}
}
}
String json = new GsonBuilder().setPrettyPrinting().create().toJson(data);
System.out.println(json);
output;
{
"idPeriodo": 121.0,
"codigo": "2014II",
"activo": false,
"tipoPeriodo": 1.0,
"fechaInicioPreMatricula": "may 1, 2014",
"fechaFinPreMatricula": "jul 1, 2014",
"fechaInicioMatricula": "jul 15, 2014",
"fechaFinMatricula": "ago 3, 2014",
"fechaInicioClase": "ago 9, 2014",
"fechaFinClase": "dic 14, 2014",
"fechaActa": "ene 15, 2015",
"fechaUltModificacion": "May 28, 2014 12:28:26 PM",
"usuarioModificacion": 1.0
}
Solution 2:
I tried a solution of @Braj in Kotlin. The idea is to convert JSON to Map, remove nulls and empty arrays, then convert Map back to JSON string.
But it has several disadvantages.
- It can only work with simple POJOs without nestings (no inner classes, lists of classes).
- It converts numbers to doubles (because
Object
is not recognized asint
). - It loses time to convert from String to String.
Alternatively you can try to use Moshi
instead of Gson
, see Broken server response handling with Moshi.
After couple of days I overcame a 1st problem for complex JSONs.
import android.support.annotation.NonNull;
import com.google.gson.Gson;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
public class GsonConverter {
private Type type;
private Gson gson;
public GsonConverter() {
type = new TypeToken<Map<String, Object>>() {
}.getType();
gson = new Gson();
}
/**
* Remove empty arrays from JSON.
*/
public String cleanJson(String jsonString) {
Map<String, Object> data = gson.fromJson(jsonString, type);
if (data == null)
return "";
Iterator<Map.Entry<String, Object>> it = data.entrySet().iterator();
traverse(it);
return gson.toJson(data);
}
private void traverse(@NonNull Iterator<Map.Entry<String, Object>> iterator) {
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
Object value = entry.getValue();
if (value == null) {
iterator.remove();
continue;
}
Class<?> aClass = value.getClass();
if (aClass.equals(ArrayList.class)) {
if (((ArrayList) value).isEmpty()) {
iterator.remove();
continue;
}
}
// Recoursively pass all tags for the next level.
if (aClass.equals(ArrayList.class)) {
Object firstItem = ((ArrayList) value).get(0);
Class<?> firstItemClass = firstItem.getClass();
// Check that we have an array of non-strings (maps).
if (firstItemClass.equals(Map.class)) {
// Array of keys and values.
@SuppressWarnings("unchecked")
ArrayList<Map<String, Object>> items = (ArrayList<Map<String, Object>>) value;
for (Map<String, Object> item : items) {
traverse(item.entrySet().iterator());
}
} else if (firstItemClass.equals(LinkedTreeMap.class)) {
// Array of complex objects.
@SuppressWarnings("unchecked")
ArrayList<LinkedTreeMap<String, Object>> items = (ArrayList<LinkedTreeMap<String, Object>>) value;
for (LinkedTreeMap<String, Object> item : items) {
traverse(item.entrySet().iterator());
}
}
} else if (aClass.equals(LinkedTreeMap.class)) {
@SuppressWarnings("unchecked")
LinkedTreeMap<String, Object> value2 = (LinkedTreeMap<String, Object>) value;
traverse(value2.entrySet().iterator());
}
}
}
}
Usage:
YourJsonObject yourJsonObject = new Gson().fromJson(new GsonConverter().cleanJson(json), YourJsonObject.class);
For those who want to use @Braj solution, here is a code in Kotlin.
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
class GsonConverter {
private val type: Type = object : TypeToken<Map<String, Any?>>() {}.type
private val gson = Gson()
private val gsonBuilder: GsonBuilder = GsonBuilder()//.setLongSerializationPolicy(LongSerializationPolicy.STRING)
fun convert(jsonString: String): String {
val data: Map<String, Any?> = gson.fromJson(jsonString, type)
val obj = data.filter { it.value != null && ((it.value as? ArrayList<*>)?.size != 0) }
val json = gsonBuilder/*.setPrettyPrinting()*/.create().toJson(obj)
println(json)
return json
}
}