Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default Value for Missing fields in Json #1005

Open
jainsahab opened this issue Jan 28, 2017 · 8 comments · May be fixed by #1006 or #2358
Open

Default Value for Missing fields in Json #1005

jainsahab opened this issue Jan 28, 2017 · 8 comments · May be fixed by #1006 or #2358

Comments

@jainsahab
Copy link

jainsahab commented Jan 28, 2017

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?

@jainsahab jainsahab linked a pull request Jan 31, 2017 that will close this issue
@lyubomyr-shaydariv
Copy link
Contributor

Are there any advantages for such an approach?

@jainsahab
Copy link
Author

@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.

@lyubomyr-shaydariv
Copy link
Contributor

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!

@tuxslayer
Copy link

tuxslayer commented Jan 25, 2018

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 :(

@drekbour
Copy link

+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.

@xeruf
Copy link

xeruf commented May 21, 2020

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.

@Marcono1234
Copy link
Collaborator

Marcono1234 commented Aug 22, 2020

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 #1005 (comment) maybe add a field annotation (e.g. @TreatMissingAsNull) to allow falling back to the type adapter.

@Euklios
Copy link

Euklios commented Mar 10, 2023

This would be even more useful now, as the current workaround modifies the already constructed object and is therefore incompatible with records.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
7 participants