Reputation: 219
Is it possible to serialize Double NaN as null with GSON?
The issue I currently have is, when I publish Double NaN, a JavaScript subscriber fails to deserialize the json mssage.
class Value {
Double value;
}
@Test
public void name() {
Value newValue = new Value();
newValue.value = Double.NaN;
Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
String string = gson.toJson(newValue);
System.out.println(string);
}
Above code prints:
{"value":NaN}
I would like:
{"value":null}
Upvotes: 0
Views: 2494
Reputation: 1787
Merely create specialized type adapter factory that overrides Float
and Double
type adapters and checks serializable values for being NaNs and maybe infinities.
public final class NumberOnlyTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory instance = new NumberOnlyTypeAdapterFactory();
private static final TypeToken<Float> floatTypeToken = TypeToken.get(Float.class);
private static final TypeToken<Double> doubleTypeToken = TypeToken.get(Double.class);
private NumberOnlyTypeAdapterFactory() {
}
public static TypeAdapterFactory getInstance() {
return instance;
}
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final Class<? super T> rawType = typeToken.getRawType();
final TypeAdapter<? extends Number> typeAdapter;
if ( rawType == Float.class ) {
typeAdapter = NumberOnlyTypeAdapter.create(gson.getDelegateAdapter(this, floatTypeToken), v -> Float.isNaN(v), v -> Float.isInfinite(v));
} else if ( rawType == Double.class ) {
typeAdapter = NumberOnlyTypeAdapter.create(gson.getDelegateAdapter(this, doubleTypeToken), v -> Double.isNaN(v), v -> Double.isInfinite(v));
} else {
return null;
}
@SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) typeAdapter;
return castTypeAdapter;
}
@AllArgsConstructor(access = AccessLevel.PRIVATE)
private static final class NumberOnlyTypeAdapter<T extends Number>
extends TypeAdapter<T> {
private final TypeAdapter<? super T> delegate;
private final Predicate<? super T> isNan;
private final Predicate<? super T> isInfinite;
private static <T extends Number> TypeAdapter<T> create(final TypeAdapter<? super T> delegate, final Predicate<? super T> isNan,
final Predicate<? super T> isInfinite) {
return new NumberOnlyTypeAdapter<T>(delegate, isNan, isInfinite)
.nullSafe();
}
@Override
public void write(final JsonWriter jsonWriter, final T value)
throws IOException {
if ( !isNan.test(value) && !isInfinite.test(value) ) {
delegate.write(jsonWriter, value);
return;
}
jsonWriter.nullValue();
}
@Override
public T read(final JsonReader jsonReader) {
throw new UnsupportedOperationException("TODO");
}
}
}
Here is a test (primitive arrays are used to test primitive floats and doubles whilst the original values are boxed as Float
and Double
respectively):
public final class NumberOnlyTypeAdapterFactoryTest {
private static final Gson gson = new GsonBuilder()
.disableInnerClassSerialization()
.disableHtmlEscaping()
.registerTypeAdapterFactory(NumberOnlyTypeAdapterFactory.getInstance())
.create();
private static Stream<Arguments> test() {
return Stream.of(
Arguments.of(Float.class, 0F, Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, (Function<Float, float[]>) v -> new float[]{ v }),
Arguments.of(Double.class, 0D, Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, (Function<Double, double[]>) v -> new double[]{ v })
);
}
@ParameterizedTest
@MethodSource
public <N extends Number, A> void test(
final Type type,
final N zeroValue,
final N nanValue,
final N negativeInfinityValue,
final N positiveInfinityValue,
final Function<? super N, ? extends A> convertToPrimitiveArray
) {
Assertions.assertEquals("0.0", gson.toJson(zeroValue));
Assertions.assertEquals("null", gson.toJson(nanValue));
Assertions.assertEquals("null", gson.toJson(negativeInfinityValue));
Assertions.assertEquals("null", gson.toJson(positiveInfinityValue));
Assertions.assertEquals("null", gson.toJson(null, type));
Assertions.assertEquals("[0.0]", gson.toJson(convertToPrimitiveArray.apply(zeroValue)));
Assertions.assertEquals("[null]", gson.toJson(convertToPrimitiveArray.apply(nanValue)));
Assertions.assertEquals("[null]", gson.toJson(convertToPrimitiveArray.apply(negativeInfinityValue)));
Assertions.assertEquals("[null]", gson.toJson(convertToPrimitiveArray.apply(positiveInfinityValue)));
}
}
Upvotes: 1