Использование дженериков с GSON

Я использую GSON для декодирования JSON в объект типа T, например.

public T decode(String json) {
    Gson gson = new Gson();
    return gson.fromJson(json, new TypeToken<T>() {}.getType());
}

Однако это возвращает исключение -

java.lang.AssertionError: Неожиданный тип. Ожидалось одно из: java.lang.reflect.ParameterizedType, java.lang.reflect.GenericArrayType, но получено: sun.reflect.generics.reflectiveObjects.TypeVariableImpl, для токена типа: T

Я думал, что с помощью TypeToken я избегаю Type Erasure.

Я ошибся?

Спасибо


person christophmccann    schedule 19.11.2010    source источник
comment
Повторяющийся вопрос: stackoverflow.com/questions/5370768 /   -  person Mark Butler    schedule 23.02.2013


Ответы (3)


Во-первых, я не вижу смысла в такой обертке Gson.

Что касается вашей проблемы, информация о самом универсальном типе T недоступна во время выполнения. Это было стерто. Он доступен только во время компиляции. Вместо этого вы хотите параметризовать его фактическим типом, например new TypeToken<List<String>>.

Из-за отсутствия овеществленных дженериков в Java (невозможно сделать T t = new T()), сам Gson вынужден использовать подход TypeToken, как вы видите. В противном случае Гсон сделал бы это гораздо элегантнее.

Чтобы иметь возможность передавать фактический тип, вы должны заново изобрести то же самое, что уже делает TypeToken. И это не имеет смысла :) Просто используйте его повторно или просто используйте Gson напрямую, не оборачивая его в какой-то вспомогательный класс, подобный этому.

person BalusC    schedule 19.11.2010
comment
Я отделял фактическое использование GSON от механизма, который его использует. У меня есть интерфейс ObjectDecoder с методом декодирования (String json). Это означает, что я могу переключаться между различными реализациями, например. GSON, Jackson и т. д. Затем я использую Guice для внедрения зависимости в механизм. - person christophmccann; 19.11.2010
comment
Лучше всего действительно повторно использовать TypeToken Gson в качестве аргумента класса или метода или, по крайней мере, заново изобретать его, если вы не хотите, чтобы сторонняя зависимость была раскрыта в вашем API. Это открытый исходный код по лицензии Apache. Основное объяснение здесь. - person BalusC; 19.11.2010

Я думаю, что первый ответ не указывает на фактическое решение: вы ДОЛЖНЫ также передать экземпляр класса вместе с T, например:

public T decode(String json, Class<T> cls) {
    Gson gson = new Gson();
    return gson.fromJson(json, cls);
}

Это потому, что 'T' здесь является типом VARIABLE, а не ссылкой на тип; и используется компилятором только для добавления неявных приведений и проверки совместимости типов. Но если вы пройдете фактический класс, его можно использовать; и компилятор проверит совместимость типов, чтобы уменьшить вероятность несоответствия.

В качестве альтернативы вы можете взять TypeToken и передать его; но TypeToken должен быть создан с использованием реального типа, а не переменной типа; Типовая переменная здесь малопригодна. Но если вы хотите обернуть вещи, вы не хотите, чтобы вызывающий использовал TypeToken (который является типом Gson).

Тот же механизм упаковки будет работать с другими библиотеками, такими как Jackson, о которых вы упомянули.

person StaxMan    schedule 04.01.2011
comment
Это не будет работать для параметризованных классов, о которых идет речь в этом вопросе. Нет такого понятия, как Map<String, Object>.class, есть только Map.class. См. Также ссылку на объяснение фона в комментарии к моему ответу. - person BalusC; 04.01.2011
comment
Да, я полностью осведомлен об этой проблеме, просто в этом вопросе не предполагалось, что это параметризованный класс, поэтому простой ответ казался достаточным. Жаль, что JDK не имеет эквивалента токена типа (ссылка на тип и т. д.; каждая библиотека/фреймворк должна определять свою собственную). - person StaxMan; 04.01.2011

Мое решение состояло в том, чтобы использовать анализатор json и разбить его на части.

public static <TT> PushObj<TT> fromJSON(String json, Class<TT> classType)
{
    JsonObject jObj = new JsonParser().parse(json).getAsJsonObject();
    String type = jObj.get("type").getAsString();
    JsonObject job = jObj.get("object").getAsJsonObject();
    TT obj = new Gson().fromJson(job, classType);
    return new PushObj<TT>(type, obj);
}

Где структура объекта: {String:Type, Generic:Object}

И переменные: jObj — это JSONObject переданной строки, а job — это JSONObject универсального объекта.

Поэтому я использовал анализатор json, чтобы получить тип отдельно и отражение для объекта.

person tommyz87    schedule 25.01.2016