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

Added Adapter Layering Feature (Java 6 Compatible) #1473

Open
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

paulkass
Copy link
Contributor

Why this is useful: Suppose you have a class with a lot of fields. Most of them can be easily interpreted by the Gson reader, possibly with a field naming strategy. But some have such weird names that the field naming strategy might not necessarily properly match them to the target properties. Especially if the JSON comes from a third party (like an API), the best course of action as of today is to create your own TypeAdapter and manually map all of the fields, including the ones that GSON could map on it's own. This is why I added the "Fill-In" feature. The registerTypeAdapterWithFillIn method allows you to manually define only the fields that Gson can not map properly, while not having to map trivial mappings.

Implementation Details: The way to register a custom adapter with Fill-in is through the GsonBuilder#registerTypeAdapterWithFillIn method. This method wraps the adapter into a constructor object that is then passed to an instance of the ReflectiveTypeAdapterFactory.Adapter class. In other words, when the read method is called on a ReflectiveTypeAdapterFactory.Adapter instance, the ObjectContstructor object calls the custom adapter and generates an instance of the target class whose fields are written to or overwritten (see the Conditions section).

When calling the create method on the ReflectiveTypeAdapterFactory responsible for producing a fill-in adapter, it will only return the adapter when the target type is or is a subclass of the desired target class. This is unlike the usual ReflectiveTypeAdapterFactory instances which serve to create a catch-all type adapter for non-standard classes.

In order to support the construction of instances while reading from the input JsonReader, the InstanceCreator and ObjectConstructor have methods that accept a JsonReader object. If not overridden by the implementation, they default to the normal construct method (for backwards compatibility).

I wrote some tests in the test/.../gson/internal/bind/LayeredAdapterTest.java class to demonstrate and test some common use cases that I thought of for this feature.

Notes:

  • The outside adapter field will overwrite any field values previously written to in the instance returned by the user-defined Adapter. To avoid this, add the @Expose annotation to the fields that you want to be protected from overwriting.
  • Due to the use of default methods in interfaces, this only supports java versions of 8+, and will fail the travis-ci check. I would really appreciate if someone pointed out a way to achieve the same thing in a backwards-compatible way without relying on Java 8 features. Also, if there is going to be a multi-release jar as suggested in java.lang.NumberFormatException: For input string: "11-ea" #1469, this can go in the java 8+ jar. I made this compatible with Java 6. See Below.

Java 6 Compatibility: I added two wrapper classes, InstanceCreatorWrapper and ObjectConstructorWrapper, that wrap the InstanceCreator and ObjectConstructor interfaces respectively. Therefore, it's backwards compatible since these creators and just implementations of their respective interfaces. However, when the fill-in part of the code tries to use them to create an instance from the contents of the JsonReader object, it will case the objects returned by the ConstructorConstructor to the proper Wrapper, and use the special methods of those classes to create the object.

* @param objectAdapter This object must implement the {@link TypeAdapter} class
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
public GsonBuilder registerTypeAdapterWithFillIn(final Type baseType, Object objectAdapter) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this method, why not create a TypeAdapter implementation that users can extend to get fill-in features.
e.g.,
ReflectiveTypeAdapter that internally will use ReflectiveTypeAdapterFactory

import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.Excluder;
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
import com.google.gson.internal.bind.TreeTypeAdapter;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid frivolous changes like adding final to method parameters, imports reordering, or formatting changes.

* class, it can expose a method that accepts a {@code JsonReader} as a parameter, allowing fill-in
* functionality.
*/
public class InstanceCreatorWrapper<T> implements InstanceCreator<T> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this class part of the public API?

import java.util.ArrayList;
import java.util.List;

public abstract class ReflectiveTypeAdapter<T> extends TypeAdapter<T> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public API?

@inder123
Copy link
Collaborator

Overall, I am still not convinced why this needs to be part of Gson, and not be part of extras package.

Overall, we prefer really small changes to the public APIs.

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

Successfully merging this pull request may close these issues.

None yet

3 participants