Given a POJO:
class Person {
@Expose
String name;
@Expose
String phone;
@Expose
String fax;
}
I want "phone" to be serialized at all times, but "fax" only when it is not null. So given a Person "John" with no phone or fax:
Current:
{ "name": "John", "phone": null, "fax": null }
What I need:
{ "name": "John", "phone": null }
Is there something like:
@Expose (serialize_if_null = false)
transient doesn't work because I still want it serialized if it has a value.
Using ExclusionStrategy, I can define the field, but I cannot seem to find a way to get the value.
Thanks
The default behavior that is implemented in Gson is that null object fields are ignored. For example, if in Employee object, we do not specify the email (i.e. email is null ) then email will not be part of serialized JSON output. Gson ignores null fields because this behavior allows a more compact JSON output format.
If there are fields in Java objects that do not wish to be serialized, we can use the @JsonIgnore annotation in the Jackson library. The @JsonIgnore can be used at the field level, for ignoring fields during the serialization and deserialization.
As you can see, Gson will ignore the unknown fields and simply match the fields that it's able to.
Gson serializer will ignore every field declared as transient: String jsonString = new Gson().
Using ExclusionStrategy, I can define the field, but I cannot seem to find a way to get the value.
Yes, it does not provide a way of determining the current field value. This is because of how Gson ReflectiveTypeAdapterFactory
works internally (the BoundField.serialized
is final
and only resolved once):
@Override public boolean writeField(Object value) throws IOException, IllegalAccessException {
if (!serialized) return false;
Object fieldValue = field.get(value);
return fieldValue != value; // avoid recursion for example for Throwable.cause
}
for (BoundField boundField : boundFields.values()) {
if (boundField.writeField(value)) {
out.name(boundField.name);
boundField.write(out, value);
}
}
This behavior cannot be changed, but I believe it's a good design choice to segregate application objects and their serialized representations (see the Data Transfer Object pattern) in order not to mix concepts and make applicaiton components loosely coupled (migrating from Gson someday would take only modifications for the respective DTO classes only).
If you're fine with having DTOs introduced to your application, then you could create separate DTO classes for both scenarios: preserving phone
and discarding fax
depending on the fax
field value.
class PersonDto {
@Expose String name;
@Expose String phone;
PersonDto(final Person person) {
name = person.name;
phone = person.phone;
}
}
class PersonDtoWithFax extends PersonDto {
@Expose String fax;
PersonDtoWithFax(final Person person) {
super(person);
fax = person.fax;
}
}
In this case the serialization is straight-forward:
final Gson gson = new GsonBuilder()
.serializeNulls()
.create();
final Person person = new Person();
person.name = "John";
final PersonDto personDto = person.fax == null
? new PersonDto(person)
: new PersonDtoWithFax(person);
System.out.println(gson.toJson(personDto));
If you don't want to introduce the segregated DTO concept per se, you probably might want to implement a custom serializer that is somewhat more complicated in implementation and probably somewhat error-prone due to property names hardcoding (but you can have good tests, of course, or extract the names from java.lang.reflect.Field
instances).
final class SpecialJsonSerializer<T>
implements JsonSerializer<T> {
private final Gson gson; // Unfortunately, Gson does not provide much from JsonSerialiationContext, so we have to get it ourselves
private final Iterable<String> excludeIfNull;
private SpecialJsonSerializer(final Gson gson, final Iterable<String> excludeIfNull) {
this.gson = gson;
this.excludeIfNull = excludeIfNull;
}
static <T> JsonSerializer<T> getSpecialJsonSerializer(final Gson gson, final Iterable<String> excludeIfNull) {
return new SpecialJsonSerializer<>(gson, excludeIfNull);
}
@Override
public JsonElement serialize(final T object, final Type type, final JsonSerializationContext context) {
// context.serialize(person, type) cannot work due to infinite recursive serialization
// therefore the backing Gson instance is used
final JsonObject jsonObject = gson.toJsonTree(object, type).getAsJsonObject();
for ( final String propertyName : excludeIfNull ) {
final JsonElement property = jsonObject.get(propertyName);
if ( property != null && property.isJsonNull() ) {
jsonObject.remove(propertyName);
}
}
return jsonObject;
}
}
I'm not really sure, but I think that creating JSON trees for serialization purposes rather than using DTOs may be slightly more expensive from the memory consumption point of view (at least because of more complicated JsonElement
structure).
// Both Gson instances must have serializeNulls()
final Gson gson = new GsonBuilder()
.serializeNulls()
.create();
final Gson gsonWrapper = new GsonBuilder()
.serializeNulls()
.registerTypeAdapter(Person.class, getSpecialJsonSerializer(gson, singletonList("fax")))
.create();
final Person person = new Person();
person.name = "John";
System.out.println(gsonWrapper.toJson(person));
Both solutions output:
{"name":"John","phone":null}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With