Auto-value-gson with an interface error, register

2020-04-21 05:43发布

I have an interface class that looks like this.

public interface Species {
    String name();
}

And a Human class that implements @AutoValue with a TypeAdapter.

@AutoValue
public abstract class Human implements Parcelable, Species {
    public static Human create(String humanVariable) {
        return new AutoValue_Human(humanVariable);
    }

    public static Human create(String name, String humanVariable) {
        return new AutoValue_Human(name, humanVariable);
    }

    public static TypeAdapter<Human> typeAdapter(Gson gson) {
        return new AutoValue_Human.GsonTypeAdapter(gson);
    }

    @SerializedName("name")
    public abstract String name();

    @Nullable
    @SerializedName("human_variable")
    public abstract String humanVariable();

    @Override
    public String name() {
       return name();
    }
}

When I pull down data from our API I get this error.

Unable to invoke no-args constructor for interface ..... Species. Register an InstanceCreator with Gson for this type may fix this problem.

I've been trying to figure out how to handle this but haven't had much luck.

I found some resources like this Serialize And Deserialize Interfaces but they don't use @AutoValue or auto-value-gson so not sure how to put everything together.

Any help would be much appreciate!

1条回答
霸刀☆藐视天下
2楼-- · 2020-04-21 06:26

InstanceCreator is not often used in Gson, suggested by Gson in such cases making some confusion, and usually can be replaced with type adapters (factories). The InstanceCreator interface can only create a default instance that won't be merged with the JSON you're trying to deserialize. For example:

{
    "name": "13289/john-doe",
    "human_variable": "John Doe"
}
public static Human create() {
    return new AutoValue_Human("anonymous", null);
}
private static final Gson gson = new GsonBuilder()
        .registerTypeAdapter(Species.class, (InstanceCreator<Species>) type -> Human.create())
        .create();
final Species species = gson.fromJson(jsonReader, Species.class);
System.out.println(species.name());

Output:

anonymous

In this case you'd bind the Species interface with the default Human instance only. According to that, species.name() would return anonymous only regardless the JSON (Gson internal ReflectiveTypeAdapterFactory.Adapter just skips all JSON fields (actually, it collects all fields first against the given field declared type, not an actual object type after the InstanceCreator-created instance is created) because it's an interface -- not sure if it's not a bug though).

What you really need here is the following steps:

  • Use com.ryanharter.auto.value:auto-value-gson:... if you're not using yet.
  • Register the Human type adapter factory created using the auto-value-gson extension.
  • Deserialize your JSON as a concrete Human instance rather than a Species instance.

For example:

@GsonTypeAdapterFactory
abstract class HumanAdapterFactory
        implements TypeAdapterFactory {

    public static TypeAdapterFactory create() {
        return new AutoValueGson_HumanAdapterFactory();
    }

}
private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(HumanAdapterFactory.create())
        .create();
final Human human = gson.fromJson(jsonReader, Human.class);
System.out.println(human.name());
System.out.println(human.humanVariable());

Output:

13289/john-doe
John Doe

This is the solution I would recommend the most.


If, for any justified reason, you really need to deserialize a JSON as an unknown Species instance and resolve its type dynamically, you could create a more complex solution. One of its "classic" solutions is resolving an object type by its special JSON property (inspired by RuntimeTypeAdapterFactory coming from the Gson extras, not published as an artifact though):

{
    "type": "human",
    "name": "13289/john-doe",
    "human_variable": "John Doe"
}
private static final Gson gson = new GsonBuilder()
        // We'll ask Gson for it ourselves
        .registerTypeAdapterFactory(HumanAdapterFactory.create())
        .registerTypeAdapterFactory(new TypeAdapterFactory() {
            @Override
            public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
                // Check whether we can support the given type
                if ( Species.class.isAssignableFrom(typeToken.getRawType()) ) {
                    final TypeAdapterFactory currentTypeAdapterFactory = this;
                    // And get the "original" type adapter
                    @SuppressWarnings("unchecked")
                    final TypeAdapter<Species> delegateTypeAdapter = (TypeAdapter<Species>) gson.getDelegateAdapter(this, typeToken);
                    final TypeAdapter<Species> speciesTypeAdapter = new TypeAdapter<Species>() {
                        // Type tokens can be static since they are immutabe
                        private /*static*/ final TypeToken<Human> humanTypeToken = TypeToken.get(Human.class);
                        // JsonParser seems to be immutable as well
                        private /*static*/ final JsonParser jsonParser = new JsonParser();

                        @Override
                        public void write(final JsonWriter out, final Species value)
                                throws IOException {
                            delegateTypeAdapter.write(out, value);
                        }

                        @Override
                        public Species read(final JsonReader in)
                                throws IOException {
                            // Caching the current value to a JSON tree
                            final JsonElement jsonElement = jsonParser.parse(in);
                            final String type = jsonElement.getAsJsonObject()
                                    .getAsJsonPrimitive("type")
                                    .getAsString();
                            final TypeAdapter<? extends Species> typeAdapter;
                            // Now trying to resolve an appropriate type adapter
                            switch ( type ) {
                            case "human":
                                typeAdapter = gson.getDelegateAdapter(currentTypeAdapterFactory, humanTypeToken);
                                break;
                            default:
                                throw new MalformedJsonException("Unknown type: " + type);
                            }
                            // At this point the JsonReader is moved formed due to the previous read, but we have the JSON tree
                            return typeAdapter.fromJsonTree(jsonElement);
                        }
                    }.nullSafe();
                    @SuppressWarnings("unchecked")
                    final TypeAdapter<T> castSpeciesTypeAdapter = (TypeAdapter<T>) speciesTypeAdapter;
                    return castSpeciesTypeAdapter;
                }
                return null;
            }
        })
        .create();
final Species species = gson.fromJson(jsonReader, Species.class);
System.out.println(species.getClass());
System.out.println(species.name());

Output:

class q43267910.AutoValue_Human
13289/john-doe

查看更多
登录 后发表回答