-
-
Save JakeWharton/0d67d01badcee0ae7bc9 to your computer and use it in GitHub Desktop.
import com.google.auto.value.AutoValue; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.Target; | |
import static java.lang.annotation.ElementType.TYPE; | |
import static java.lang.annotation.RetentionPolicy.RUNTIME; | |
/** | |
* Marks an {@link AutoValue @AutoValue}-annotated type for proper Gson serialization. | |
* <p> | |
* This annotation is needed because the {@linkplain Retention retention} of {@code @AutoValue} | |
* does not allow reflection at runtime. | |
*/ | |
@Target(TYPE) | |
@Retention(RUNTIME) | |
public @interface AutoGson { | |
} |
import com.google.gson.Gson; | |
import com.google.gson.TypeAdapter; | |
import com.google.gson.TypeAdapterFactory; | |
import com.google.gson.reflect.TypeToken; | |
public final class AutoValueAdapterFactory implements TypeAdapterFactory { | |
@SuppressWarnings("unchecked") | |
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { | |
Class<? super T> rawType = type.getRawType(); | |
if (!rawType.isAnnotationPresent(AutoGson.class)) { | |
return null; | |
} | |
String packageName = rawType.getPackage().getName(); | |
String className = rawType.getName().substring(packageName.length() + 1).replace('$', '_'); | |
String autoValueName = packageName + ".AutoValue_" + className; | |
try { | |
Class<?> autoValueType = Class.forName(autoValueName); | |
return (TypeAdapter<T>) gson.getAdapter(autoValueType); | |
} catch (ClassNotFoundException e) { | |
throw new RuntimeException("Could not load AutoValue type " + autoValueName, e); | |
} | |
} | |
} |
import com.google.auto.value.AutoValue; | |
import com.google.gson.Gson; | |
import com.google.gson.GsonBuilder; | |
public class Main { | |
public static void main(String... args) { | |
Gson gson = new GsonBuilder() | |
.registerTypeAdapterFactory(new AutoValueAdapterFactory()) | |
.create(); | |
Test inTest = Test.of("John", "Doe", 100); | |
System.out.println("IN: " + inTest); | |
String json = gson.toJson(inTest); | |
System.out.println("JSON: " + json); | |
Test outTest = gson.fromJson(json, Test.class); | |
System.out.println("OUT: " + outTest); | |
} | |
@AutoValue @AutoGson | |
public abstract static class Test { | |
public static Test of(String firstName, String lastName, int age) { | |
return new AutoValue_Main_Test(firstName, lastName, age); | |
} | |
public abstract String firstName(); | |
public abstract String lastName(); | |
public abstract int age(); | |
} | |
} | |
If someone need some simple test for @thenuge solution
import com.google.auto.value.AutoValue;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.junit.Before;
import org.junit.Test;
import static com.google.common.truth.Truth.assert_;
public class AutoValueAdapterFactoryTest {
private Gson mGson;
@AutoValue
@AutoGson(autoValueClass = AutoValue_AutoValueAdapterFactoryTest_Some.class)
public abstract static class Some {
public static Some of(String firstName, String lastName, int age) {
return new AutoValue_AutoValueAdapterFactoryTest_Some(firstName, lastName, age);
}
public abstract String firstName();
public abstract String lastName();
public abstract int age();
}
@Before
public void setUp() throws Exception {
mGson = new GsonBuilder()
.registerTypeAdapterFactory(new AutoValueAdapterFactory())
.create();
}
@Test
public void testParsing() throws Exception {
final Some inSome = Some.of("John", "Doe", 100);
final String json = mGson.toJson(inSome);
final Some outSome = mGson.fromJson(json, Some.class);
assert_().that(outSome).isEqualTo(inSome);
}
}
Rather than using a new Annotation, testing for an abstract class may be a little more automatic.
public final class AutoValueAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<? super T> rawType = type.getRawType();
// If it's an abstract class, it's likely to be an AutoValue
int m = rawType.getModifiers();
if (!Modifier.isAbstract(m)) {
return null;
}
String packageName = rawType.getPackage().getName();
String className = rawType.getName().substring(packageName.length() + 1).replace('$', '_');
String autoValueName = packageName + ".AutoValue_" + className;
try {
Class<?> autoValueType = Class.forName(autoValueName);
return (TypeAdapter<T>) gson.getAdapter(autoValueType);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Could not load AutoValue type " + autoValueName, e);
}
}
}
Hi!
I'm trying to test this chunk of code and I'm always bumping into a no-args contructor problem.
Looking at the code generated by AutoValue, there is indeed no no-args constructor (as it should, afaik).
What am I missing?
Thanks!
@codedance That is far too presumptuous to work at scale.
Is there a way how to use @SerializedName with AutoValue and this TypeAdapterFactory?
@semanticer https://github.com/rharter/auto-value-gson supports @SerializedName
.
Also interested in adding SerializedName.
Ok, what about Builders ? AutoValue for android support builder pattern, however when i use it with your @AutoGson it's giving me objects full of nulls. Is there any good way to go or i need to write my own solution ?
Does this work with generics?
@AutoValue @AutoGson
public abstract static class Some<T> {
public static Some <X> of(String firstName, String lastName, int age, X genericData) {
return new AutoValue_Main_Test<>(firstName, lastName, age, genericData);
}
public abstract String firstName();
public abstract String lastName();
public abstract int age();
public abstract T genericData();
}
When you do something like:
public class People {
List<Some<Map<String,String>> someList;
}
And try to parse a "People" json, will the generic be resolved?
@danielesegato https://github.com/rharter/auto-value-gson works with generics
Very useful, thanks! I made a slight adjustment to make it work with obfuscation, since using hardcoded strings to find the AutoValue-generated class no longer works once you start obfuscating class names. It just takes a class reference instead:
Then, you just need to decorate your AutoValue class like so (following your example):