Gabriel Santos Carvalho Gabriel Santos Carvalho - 10 days ago 6
Java Question

How to serialize an object as a single value in Gson?

When you have a class to be converted to a json, if it contains a BigDecimal attribute, it will return a json like this:

Response {
BigDecimal price;
}
//json:
{
price: 20.20
}


Note that BigDecimal is a class. Its behavior is like an primitive (integer, float).

I want to produce the same behavior (a class return a single information to json)

Example:

class Response {
Money value
}

Money {
BigDecimal price;
}

//What is returning:
{
value : { price: 20.20 }
}

//What I want:
{
value : 20.20
}

Answer

Gson doesn't have such a feature out of the box. You'll need to implement it yourself.

If it's just for the Response type, you can simply implement your own TypeAdapter.

class ResponseTypeAdapter extends TypeAdapter<Response> {
    @Override
    public void write(JsonWriter out, Response value) throws IOException {
        out.beginObject();
        out.name("value");
        // check for null, if applicable, and use a default value, or don't write anything at all
        out.value(value.getValue().getPrice());
        out.endObject();
    }

    @Override
    public Response read(JsonReader in) throws IOException {
        // implement the deserialization
    }
}

Then register it.

Gson gson = new GsonBuilder().registerTypeAdapter(Response.class, new ResponseTypeAdapter()).create();
// test it
String json = gson.toJson(new Response(new Money(new BigDecimal("20.20"))));

This would now serialize to

{"value":20.20}

If you can use Jackson, it comes with a @JsonValue annotation which does this for you. For example,

class Money {
    private final BigDecimal price;
    public Money(BigDecimal bigDecimal) {
        this.price = bigDecimal;
    }
    @JsonValue // <<< this
    public BigDecimal getPrice() {
        return price;
    }
}

used with

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(new Response(new Money(new BigDecimal("20.20"))));

will generate

{"value":20.20}

The javadoc states

Marker annotation similar to javax.xml.bind.annotation.XmlValue that indicates that results of the annotated "getter" method (which means signature must be that of getters; non-void return type, no args) is to be used as the single value to serialize for the instance. Usually value will be of a simple scalar type (String or Number), but it can be any serializable type (Collection, Map or Bean).

At most one method of a Class can be annotated with this annotation; if more than one is found, an exception may be thrown. Also, if method signature is not compatible with Getters, an exception may be thrown (whether exception is thrown or not is an implementation detail (due to filtering during introspection, some annotations may be skipped) and applications should not rely on specific behavior).