Gson: Default Value for Missing fields in Json

Created on 28 Jan 2017  路  7Comments  路  Source: google/gson

I know, We can achieve this by just setting up the field with the expected value, but people may not always remember that. Can Gson provide us with some mechanism of handling the fields which are not present in JSON?

Most helpful comment

Vote for the issue.

I was hoping to fix that for type adapters. For example, I have an Option[T] field in my type, and want to map all missing/null values to None.
But after implementing such type adapter it still doesn't work: Gson just don't call type adapter for a missing field, it sets field value to null :(

All 7 comments

Are there any advantages for such an approach?

@lyubomyr-shaydariv if your question is why one need such functionality as part of Gson library, then my answer would be, my library should be able to initialize default values in the objects.
Ex: one of the use case I thought, I can keep my field as Optional and gson should be able to initialize it with Optional.present or Optional.absent based on the node present in JSON string.

If your question is around to change the approach to achieve the same functionality, I am open to suggestions and we can improve upon the approach.

FYI, I created a pull request for this as well.

All you need to cover such a case is a custom type adapter: missing Optional fields can be automatically set to Optional.empty() without changes in the core. An example inspired by the interceptors demo:

final class PostProcessingTypeAdapterFactory
        implements TypeAdapterFactory {

    private final Iterable<? extends Consumer<Object>> postProcessors;

    private PostProcessingTypeAdapterFactory(final Iterable<? extends Consumer<Object>> postProcessors) {
        this.postProcessors = postProcessors;
    }

    static TypeAdapterFactory getPostProcessingTypeAdapterFactory(final Consumer<Object>... postProcessors) {
        return new PostProcessingTypeAdapterFactory(asList(postProcessors.clone()));
    }

    static TypeAdapterFactory getPostProcessingTypeAdapterFactory(final Iterable<? extends Consumer<Object>> postProcessors) {
        return new PostProcessingTypeAdapterFactory(postProcessors);
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        final TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, typeToken);
        return new TypeAdapter<T>() {
            @Override
            public void write(final JsonWriter out, final T value)
                    throws IOException {
                delegateAdapter.write(out, value);
            }

            @Override
            public T read(final JsonReader in)
                    throws IOException {
                final T value = delegateAdapter.read(in);
                for ( final Consumer<Object> postProcessor : postProcessors ) {
                    postProcessor.accept(value);
                }
                return value;
            }
        };
    }

}
// This processor can be strategy-driven, but implemented stupid-simple just for the demo
final class VerySimpleMissingFieldsPostProcessor
        implements Consumer<Object> {

    private static final Consumer<Object> verySimpleMissingFieldsPostProcessor = new VerySimpleMissingFieldsPostProcessor();

    private VerySimpleMissingFieldsPostProcessor() {
    }

    static Consumer<Object> getVerySimpleMissingFieldsPostProcessor() {
        return verySimpleMissingFieldsPostProcessor;
    }

    @Override
    public void accept(final Object o) {
        try {
            if ( o instanceof Foo ) {
                final Field[] declaredFields = Foo.class.getDeclaredFields();
                for ( final Field field : declaredFields ) {
                    if ( field.getType() == Optional.class ) {
                        field.setAccessible(true);
                        if ( field.get(o) == null ) {
                            field.set(o, Optional.empty());
                        }
                    }
                }
            }
        } catch ( final IllegalAccessException ex ) {
            throw new RuntimeException(ex);
        }
    }

}
final class Foo {

    String bar;

    Optional<String> baz;

}
final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(getPostProcessingTypeAdapterFactory(getVerySimpleMissingFieldsPostProcessor()))
        .create();
final Foo foo = gson.fromJson("{\"bar\":\"bar-value\"}", Foo.class);
System.out.println(foo.bar);
System.out.println(foo.baz.orElse("missing!")); // no NPE

Output:

bar-value
missing!

Vote for the issue.

I was hoping to fix that for type adapters. For example, I have an Option[T] field in my type, and want to map all missing/null values to None.
But after implementing such type adapter it still doesn't work: Gson just don't call type adapter for a missing field, it sets field value to null :(

+1 for the feature (I want Collections to default to empty not null).
new GsonBuilder().treatMissingLikeNull()

For the record I looked at your PR. I'm not keen on baking that defaultType registry into the core builder at all. Better would be to detect a missing field (as the object is ended) and supply to the TypeAdapters as if it were explicitly null. This small change allows the rest of the chain-of-responsibility handling to process a null according to existing rules. It would be very simple to write some delegating TypeAdapterFactory that then implements your default registry (but now configurable per object for instance).

I'd PR this myself but I'm not blind to the number of ignored PRs here.

We can achieve this by just setting up the field with the expected value

How do I set a field up with the expected value? I am currently getting com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Cannot build Settings, some of required attributes are not set and would like to specify default values.

Not a maintainer, but are you interested in this only for types where null values make no sense (or are discouraged), e.g. java.util.Optional or Vavr's io.vavr.control.Option? Or do you want this also for other types where null values might be acceptable in certain use cases, e.g. a java.util.List?

If only the former is the case maybe one solution would be to add a createDefaultValue() method to TypeAdapter whose default implementation returns null, but when you are for example writing a custom type adapter for Optional you could make it return Optional.empty().
Though that would require checking all the time that all values for all fields are present even if none of the type adapters overwrite createDefaultValue() causing some overhead.

Or similar to https://github.com/google/gson/issues/1005#issuecomment-591480938 maybe add a field annotation (e.g. @TreatMissingAsNull) to allow falling back to the type adapter.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

JakeWharton picture JakeWharton  路  39Comments

GoogleCodeExporter picture GoogleCodeExporter  路  19Comments

GoogleCodeExporter picture GoogleCodeExporter  路  20Comments

GoogleCodeExporter picture GoogleCodeExporter  路  15Comments

GoogleCodeExporter picture GoogleCodeExporter  路  17Comments