-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Read JsonAdapter Annotation From Parent Inteface #1370
Comments
What if you add a custom type adapter factory that searches for final class InterfaceJsonAdapterTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory instance = new InterfaceJsonAdapterTypeAdapterFactory();
private static final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
private InterfaceJsonAdapterTypeAdapterFactory() {
}
static TypeAdapterFactory get() {
return instance;
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final Class<? super T> rawType = typeToken.getRawType();
for ( Class<?> c = rawType; c != null && c != Object.class; c = c.getSuperclass() ) {
if ( c.getAnnotation(JsonAdapter.class) != null ) {
return null;
}
for ( final Class<?> i : c.getInterfaces() ) {
@Nullable
final JsonAdapter interfaceJsonAdapter = i.getAnnotation(JsonAdapter.class);
if ( interfaceJsonAdapter != null ) {
return adaptTypeAdapterCandidate(gson, typeToken, interfaceJsonAdapter);
}
}
}
return null;
}
/**
* @see com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory#getTypeAdapter(ConstructorConstructor, Gson, TypeToken, JsonAdapter)
*/
private static <T> TypeAdapter<T> adaptTypeAdapterCandidate(final Gson gson, final TypeToken<T> typeToken, final JsonAdapter jsonAdapter) {
final Object candidate = createTypeAdapterCandidate(jsonAdapter);
final TypeAdapter<T> typeAdapter;
if ( candidate instanceof TypeAdapter ) {
@SuppressWarnings("unchecked")
final TypeAdapter<T> castInstance = (TypeAdapter<T>) candidate;
typeAdapter = castInstance;
} else if ( candidate instanceof TypeAdapterFactory ) {
typeAdapter = ((TypeAdapterFactory) candidate).create(gson, typeToken);
} else if ( candidate instanceof JsonSerializer || candidate instanceof JsonDeserializer ) {
@SuppressWarnings("unchecked")
final JsonSerializer<T> serializer = candidate instanceof JsonSerializer ? (JsonSerializer<T>) candidate : null;
@SuppressWarnings("unchecked")
final JsonDeserializer<T> deserializer = candidate instanceof JsonDeserializer ? (JsonDeserializer<T>) candidate : null;
typeAdapter = new TreeTypeAdapter<>(serializer, deserializer, gson, typeToken, null);
} else {
throw new IllegalArgumentException("Cannot adapt " + candidate);
}
return typeAdapter != null && jsonAdapter.nullSafe() ? typeAdapter.nullSafe() : typeAdapter;
}
private static Object createTypeAdapterCandidate(final JsonAdapter jsonAdapter) {
try {
return unsafeAllocator.newInstance(jsonAdapter.value());
} catch ( final Exception ex ) {
throw new RuntimeException(ex);
}
}
} final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(InterfaceJsonAdapterTypeAdapterFactory.get())
.create(); Not well-tested. |
This is an interesting idea, and one that I will explore a little more, though, I honestly just wish that it was built into gson to be able to look this information up and use it so that I don't have to give additional instructions for consumers to register a type adapter or type adapter factory. |
What happens when there's two interfaces with two json adapters? What happens when the interface is from a library and you want to serialize it differently? What happens when the interface and the class are from a library and you want to serialize it differently? I don't think adding this behavior would be a good idea. |
I think that it works as long as you always allow the ability to override, which would maintain the behavior of how it is currently implemented with a concrete class. If you provide the annotation, you can still override it with specific Gson configuration (through GsonBuilder). There is also a lot of power here in that common libraries could allow for certain data to be serialized by gson by default and not have multiple consumers take on that burden unless they specifically want to. This allows for a more consistent serialization and higher code re-use.
This is probably the justification for not doing it that would make the most sense to me since it is very difficult to come up with the right behavior for this. I would even be in favor of opting out of using the JsonAdapter annotation if the target class implements multiple interfaces or something like that, and I feel that it would be a justifiable instance to not use the JsonAdapter value since GSON would not be able to determine which one to use. |
This is sort of a feature request and sort of just asking for other approaches of potentially handling this.
There are a couple of design choices that we have made for specific reason that I won't go into, but there are a few things to note about our use case:
For this reason, serialization has always been a bit difficult because the interface is not a reliable contract for serialization (what are the default implementation field names, etc.) and those likely wouldn't work with other implementations of the model object, etc.
For that reason, I was hoping to provide on the interface a default TypeAdapter that serializes to the default instance of the object. This would allow for implementations of the interface to be able to get back to an instance of the object if they didn't have any additional logic that they were wanting.
I looked at JsonAdapter, and it seems to provide what I'm looking for, it just does not get pulled from the interface.
In the above example, B class shows that the JsonAdapter annotation is not getting pulled from the interface. The C class shows that the annotation and the TypeAdapter do work, but only if it's on the implementation object.
Basically, I'm looking for a way to default in the serialization of all implementations of an interface unless the consumer chooses to specifically override it (which could be probable if they are implementing multiple interfaces).
The text was updated successfully, but these errors were encountered: