Gson: 00 as decimal places when serialize Double

Created on 27 Aug 2020  路  3Comments  路  Source: google/gson

Hi team,

For a specific reason, I want 2 decimal places for my double when serializing into JSON.
Something like:

9900 --> 9900.00
9900.0 --> 9900.00
9900.1234 --> 9900.12

My best result so far:

9900 --> 9900.0
9900.0 --> 9900.0
9900.1234 --> 9900.12

My DoubleAdapter.java, please advice. Thanks a lot!

class DoubleAdapter extends TypeAdapter<Double>  {

    @Override
    public void write(JsonWriter jsonWriter, Double aDouble) throws IOException {
        DecimalFormat df = new DecimalFormat("0.00");
        jsonWriter.value(Double.valueOf(df.format(aDouble)));
    }

    @Override
    public Double read(JsonReader jsonReader) throws IOException {
        return jsonReader.nextDouble();
    }
}

Most helpful comment

It looks like there are at least two hacky workarounds:

  1. Use JsonWriter.jsonValue(String)
    Note however that this method should be avoided unless you know for sure that the JsonWriter is not a JsonTreeWriter at runtime, see #1651.
  2. Create a custom java.lang.Number subclass which implements toString() by using a DecimalFormat.
    However this relies on the implementation detail that Gson internally uses Number.toString(). Additionally you should implement all Number methods to get correct behavior in case the JsonWriter is a JsonTreeWriter at runtime:
    ```java
    public class FormattedDouble extends Number {
    private static final long serialVersionUID = 1L;
    // ThreadLocal because DecimalFormat is not thread-safe
    private static final ThreadLocal<DecimalFormat> format =
        // Specify DecimalFormatSymbols to make code independent from default Locale
        ThreadLocal.withInitial(() -> new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.ENGLISH)));

    private final Double delegate;

    public FormattedDouble(Double value) {
        // Possibly also have to add a Objects.requireNonNull here
        this.delegate = value;
    }

    @Override public byte byteValue() {
        return delegate.byteValue();
    }

    @Override public short shortValue() {
        return delegate.shortValue();
    }

    @Override public int intValue() {
        return delegate.intValue();
    }

    @Override public long longValue() {
        return delegate.longValue();
    }

    @Override public float floatValue() {
        return delegate.floatValue();
    }

    @Override public double doubleValue() {
        return delegate.doubleValue();
    }

    @Override public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        } else if (obj instanceof FormattedDouble) {
            return ((FormattedDouble) obj).delegate.equals(delegate);
        } else {
            return false;
        }
    }

    @Override public int hashCode() {
        return 31 * delegate.hashCode() + format.get().hashCode();
    }

    @Override public String toString() {
        return format.get().format((double) delegate);
    }
}
```
And then use it like this:
```java
@Override
public void write(JsonWriter jsonWriter, Double aDouble) throws IOException {
    // Possibly also have to add a null check here
    jsonWriter.value(new FormattedDouble(aDouble));
}
```

All 3 comments

JsonWriter does not support it AFAIR (and I do believe it's totally fine). Just curious: what's the rationale of your case?

It looks like there are at least two hacky workarounds:

  1. Use JsonWriter.jsonValue(String)
    Note however that this method should be avoided unless you know for sure that the JsonWriter is not a JsonTreeWriter at runtime, see #1651.
  2. Create a custom java.lang.Number subclass which implements toString() by using a DecimalFormat.
    However this relies on the implementation detail that Gson internally uses Number.toString(). Additionally you should implement all Number methods to get correct behavior in case the JsonWriter is a JsonTreeWriter at runtime:
    ```java
    public class FormattedDouble extends Number {
    private static final long serialVersionUID = 1L;
    // ThreadLocal because DecimalFormat is not thread-safe
    private static final ThreadLocal<DecimalFormat> format =
        // Specify DecimalFormatSymbols to make code independent from default Locale
        ThreadLocal.withInitial(() -> new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.ENGLISH)));

    private final Double delegate;

    public FormattedDouble(Double value) {
        // Possibly also have to add a Objects.requireNonNull here
        this.delegate = value;
    }

    @Override public byte byteValue() {
        return delegate.byteValue();
    }

    @Override public short shortValue() {
        return delegate.shortValue();
    }

    @Override public int intValue() {
        return delegate.intValue();
    }

    @Override public long longValue() {
        return delegate.longValue();
    }

    @Override public float floatValue() {
        return delegate.floatValue();
    }

    @Override public double doubleValue() {
        return delegate.doubleValue();
    }

    @Override public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        } else if (obj instanceof FormattedDouble) {
            return ((FormattedDouble) obj).delegate.equals(delegate);
        } else {
            return false;
        }
    }

    @Override public int hashCode() {
        return 31 * delegate.hashCode() + format.get().hashCode();
    }

    @Override public String toString() {
        return format.get().format((double) delegate);
    }
}
```
And then use it like this:
```java
@Override
public void write(JsonWriter jsonWriter, Double aDouble) throws IOException {
    // Possibly also have to add a null check here
    jsonWriter.value(new FormattedDouble(aDouble));
}
```

@Marcono1234
oh, I totally forgot that JsonWriter can write arbitrary Number implementations relying on their possibly unsafe toString methods that may cause Gson to produce illegal JSONs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

danieleguiducci picture danieleguiducci  路  34Comments

RobMans426 picture RobMans426  路  20Comments

GoogleCodeExporter picture GoogleCodeExporter  路  25Comments

GoogleCodeExporter picture GoogleCodeExporter  路  20Comments

LucianWang picture LucianWang  路  42Comments