I\'m reading a JSON response with Gson, which returns somtimes a NumberFormatException
because an expected int
value is set to an empty string. Now
At first, I tried to write a general custom type adaptor for Integer values, to catch the NumberFormatException
and return 0, but Gson doesn't allow TypeAdaptors for primitive Types:
java.lang.IllegalArgumentException: Cannot register type adapters for class java.lang.Integer
After that I introduced a new Type FooRuntime
for the runtime
field, so the Foo
class now looks like this:
public class Foo
{
private String name;
private FooRuntime runtime;
public int getRuntime()
{
return runtime.getValue();
}
}
public class FooRuntime
{
private int value;
public FooRuntime(int runtime)
{
this.value = runtime;
}
public int getValue()
{
return value;
}
}
A type adaptor handles the custom deserialization process:
public class FooRuntimeTypeAdapter implements JsonDeserializer<FooRuntime>, JsonSerializer<FooRuntime>
{
public FooRuntime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
int runtime;
try
{
runtime = json.getAsInt();
}
catch (NumberFormatException e)
{
runtime = 0;
}
return new FooRuntime(runtime);
}
public JsonElement serialize(FooRuntime src, Type typeOfSrc, JsonSerializationContext context)
{
return new JsonPrimitive(src.getValue());
}
}
Now it's necessary to use GsonBuilder
to register the type adapter, so an empty string is interpreted as 0 instead of throwing a NumberFormatException
.
String input = "{\n" +
" \"name\" : \"Test\",\n" +
" \"runtime\" : \"\"\n" +
"}";
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(FooRuntime.class, new FooRuntimeTypeAdapter());
Gson gson = builder.create();
Foo foo = gson.fromJson(input, Foo.class);
Here is example I did for Long type.This is better option:
public class LongTypeAdapter extends TypeAdapter<Long>{
@Override
public Long read(JsonReader reader) throws IOException {
if(reader.peek() == JsonToken.NULL){
reader.nextNull();
return null;
}
String stringValue = reader.nextString();
try{
Long value = Long.valueOf(stringValue);
return value;
}catch(NumberFormatException e){
return null;
}
}
@Override
public void write(JsonWriter writer, Long value) throws IOException {
if (value == null) {
writer.nullValue();
return;
}
writer.value(value);
}
}
Register the adapter upon creation of the gson util:
Gson gson = new GsonBuilder().registerTypeAdapter(Long.class, new LongTypeAdapter()).create();
You can refer to this link for more.
Quick and easy workaround - Just change your member type field of runtime to String and access it via getter that returns runtime as an int:
public class Foo
{
private String name;
private String runtime;
public int getRuntime(){
if(runtime == null || runtime.equals("")){
return 0;
}
return Integer.valueOf(trackId);
}
}
=> no json deserialization neccessary
This solution works for Double types. This will only work for non-primitive types:
public class DoubleGsonTypeAdapter implements JsonDeserializer<Double> {
@Override
public Double deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
Double result = null;
try {
result = jsonElement.getAsDouble();
} catch (NumberFormatException e) {
return result;
}
return result;
}
}
Model:
@SerializedName("rateOfInterest")
public Double rateOfInterest;
@SerializedName("repaymentTenure")
public Double repaymentTenure;
@SerializedName("emiAmount")
public Double emiAmount;
Retrofit client:
Gson gson = new GsonBuilder().registerTypeAdapter(Double.class, new DoubleGsonTypeAdapter()) .create();
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
I've made this TypeAdapter which check for empty strings and return 0
public class IntegerTypeAdapter extends TypeAdapter<Number> {
@Override
public void write(JsonWriter jsonWriter, Number number) throws IOException {
if (number == null) {
jsonWriter.nullValue();
return;
}
jsonWriter.value(number);
}
@Override
public Number read(JsonReader jsonReader) throws IOException {
if (jsonReader.peek() == JsonToken.NULL) {
jsonReader.nextNull();
return null;
}
try {
String value = jsonReader.nextString();
if ("".equals(value)) {
return 0;
}
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
}
It might help you to always assume a default value of 0
for the field runtime
in case of a NumberFormatException, since it can be the only source of error.