Auto-value-gson с ошибкой интерфейса, зарегистрировать InstanceCreator?

У меня есть класс интерфейса, который выглядит так.

public interface Species {
    String name();
}

И Human класс, реализующий @AutoValue с помощью 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();
    }
}

Когда я извлекаю данные из нашего API, я получаю эту ошибку.

Невозможно вызвать конструктор без аргументов для интерфейса ..... Виды. Зарегистрируйте InstanceCreator в Gson, поскольку этот тип может решить эту проблему.

Я пытался понять, как с этим справиться, но безуспешно.

Я нашел несколько ресурсов вроде этого Сериализовать и десериализовать интерфейсы но они не используют @AutoValue или auto-value-gson, поэтому не знаю, как все вместе собрать.

Любая помощь будет очень признательна!


person Claud    schedule 07.04.2017    source источник


Ответы (1)


InstanceCreator не часто используется в Gson, предложенный Gson в таких случаях, внося некоторую путаницу, и обычно может быть заменен адаптерами типов (фабриками). Интерфейс InstanceCreator может создать только экземпляр по умолчанию, который не будет объединен с JSON, который вы пытаетесь десериализовать. Например:

{
    "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());

Выход:

анонимный

В этом случае вы бы связали интерфейс Species только с экземпляром по умолчанию Human. В соответствии с этим species.name() вернет anonymous только независимо от JSON (внутренний ReflectiveTypeAdapterFactory.Adapter Gson просто пропускает все поля JSON (фактически, он сначала собирает все поля для заданного объявленного типа поля, а не фактического типа объекта после создания InstanceCreator-created экземпляра) потому что это интерфейс - хотя не уверен, что это не ошибка).

Что вам действительно нужно, так это следующие шаги:

  • Используйте com.ryanharter.auto.value:auto-value-gson:..., если вы еще не используете.
  • Зарегистрируйте фабрику адаптеров типа Human, созданную с использованием расширения auto-value-gson.
  • Десериализуйте свой JSON как конкретный Human экземпляр, а не Species.

Например:

@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());

Выход:

13289 / john-doe
Джон Доу

Это решение, которое я бы рекомендовал больше всего.


Если по какой-либо обоснованной причине вам действительно нужно десериализовать JSON как неизвестный Species экземпляр и динамически разрешить его тип, вы можете создать более сложное решение. Одно из его «классических» решений - определение типа объекта с помощью его специального свойства JSON (навеянного RuntimeTypeAdapterFactory из дополнительных компонентов Gson, но не опубликованных как артефакт):

{
    "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());

Выход:

класс q43267910.AutoValue_Human
13289 / john-doe

person Lyubomyr Shaydariv    schedule 07.04.2017