Instantly share code, notes, and snippets.

Embed
What would you like to do?
A Gson TypeAdapterFactory which allows serialization of @autovalue types. Apache 2 licensed.
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();
}
}
@thenuge

This comment has been minimized.

Show comment
Hide comment
@thenuge

thenuge Nov 9, 2014

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:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AutoGson {
  // A reference to the AutoValue-generated class (e.g. AutoValue_MyClass). This is
  // necessary to handle obfuscation of the class names.
  public Class autoValueClass();
}
public class AutoValueTypeAdapterFactory implements TypeAdapterFactory {
  @Override
  public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
    Class<T> rawType = (Class<T>) type.getRawType();

    AutoGson annotation = rawType.getAnnotation(AutoGson.class);
    // Only deserialize classes decorated with @AutoGson.
    if (annotation == null) {
      return null;
    }

    return (TypeAdapter<T>) gson.getAdapter(annotation.autoValueClass());
  }
}

Then, you just need to decorate your AutoValue class like so (following your example):

  @AutoValue 
  @AutoGson(autoValueClass = AutoValue_Main_Test.class)
  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();
  }

thenuge commented Nov 9, 2014

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:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AutoGson {
  // A reference to the AutoValue-generated class (e.g. AutoValue_MyClass). This is
  // necessary to handle obfuscation of the class names.
  public Class autoValueClass();
}
public class AutoValueTypeAdapterFactory implements TypeAdapterFactory {
  @Override
  public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
    Class<T> rawType = (Class<T>) type.getRawType();

    AutoGson annotation = rawType.getAnnotation(AutoGson.class);
    // Only deserialize classes decorated with @AutoGson.
    if (annotation == null) {
      return null;
    }

    return (TypeAdapter<T>) gson.getAdapter(annotation.autoValueClass());
  }
}

Then, you just need to decorate your AutoValue class like so (following your example):

  @AutoValue 
  @AutoGson(autoValueClass = AutoValue_Main_Test.class)
  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();
  }
@jacek-marchwicki

This comment has been minimized.

Show comment
Hide comment
@jacek-marchwicki

jacek-marchwicki Dec 23, 2014

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);
    }

}

jacek-marchwicki commented Dec 23, 2014

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);
    }

}
@codedance

This comment has been minimized.

Show comment
Hide comment
@codedance

codedance Apr 3, 2015

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);
    }
  }
}

codedance commented Apr 3, 2015

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);
    }
  }
}
@pedro-ribeiro

This comment has been minimized.

Show comment
Hide comment
@pedro-ribeiro

pedro-ribeiro Jun 11, 2015

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!

pedro-ribeiro commented Jun 11, 2015

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!

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Jul 21, 2015

@codedance That is far too presumptuous to work at scale.

Owner

JakeWharton commented Jul 21, 2015

@codedance That is far too presumptuous to work at scale.

@semanticer

This comment has been minimized.

Show comment
Hide comment
@semanticer

semanticer Dec 17, 2015

Is there a way how to use @SerializedName with AutoValue and this TypeAdapterFactory?

semanticer commented Dec 17, 2015

Is there a way how to use @SerializedName with AutoValue and this TypeAdapterFactory?

@efung

This comment has been minimized.

Show comment
Hide comment
@efung

efung commented May 19, 2016

@oldergod

This comment has been minimized.

Show comment
Hide comment
@oldergod

oldergod Sep 22, 2016

Also interested in adding SerializedName.

oldergod commented Sep 22, 2016

Also interested in adding SerializedName.

@lukasz-gosiewski

This comment has been minimized.

Show comment
Hide comment
@lukasz-gosiewski

lukasz-gosiewski Dec 5, 2016

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 ?

lukasz-gosiewski commented Dec 5, 2016

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 ?

@danielesegato

This comment has been minimized.

Show comment
Hide comment
@danielesegato

danielesegato Feb 3, 2017

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 commented Feb 3, 2017

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?

@Ajibola

This comment has been minimized.

Show comment
Hide comment

Ajibola commented Dec 12, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment