Intereting Posts

Kotlin: параметр типа Reified делает Gson неудачным

Я столкнулся с странным поведением, используя дескрипцию Gson в функции с типом reified. Это происходит только тогда, когда interfaces задействованы в параметре типа.

Возьмите следующий код:

 val toBeSerialized = listOf("1337") with(Gson()) { val ser = toJson(toBeSerialized) val deser = fromJson<List<Serializable>>(ser) } 

Строка номер 4 использует пользовательскую функцию расширения Gson.fromJson(json: String): T

Он терпит неудачу, если T определено как reified:

 inline fun <reified T> Gson.fromJson(json: String): T = fromJson<T>(json, object : TypeToken<T>() {}.type) 

И он работает, если он определен как обычный тип параметра:

 fun <T> Gson.fromJson(json: String): T = fromJson<T>(json, object : TypeToken<T>() {}.type) 

(Обратите внимание, что создание T reified здесь не имеет смысла, просто хочу понять его влияние в специальном случае использования)

Исключение при использовании reified выглядит следующим образом:

 Exception in thread "main" java.lang.RuntimeException: Unable to invoke no-args constructor for ? extends java.io.Serializable. Registering an InstanceCreator with Gson for this type may fix this problem. at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:226) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:210) at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41) at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82) at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61) at com.google.gson.Gson.fromJson(Gson.java:888) at com.google.gson.Gson.fromJson(Gson.java:853) at com.google.gson.Gson.fromJson(Gson.java:802) at SoTestsKt.main(SoTests.kt:25) Caused by: java.lang.UnsupportedOperationException: Interface can't be instantiated! Interface name: java.io.Serializable at com.google.gson.internal.UnsafeAllocator.assertInstantiable(UnsafeAllocator.java:117) at com.google.gson.internal.UnsafeAllocator$1.newInstance(UnsafeAllocator.java:49) at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:223) ... 8 more 

Solutions Collecting From Web of "Kotlin: параметр типа Reified делает Gson неудачным"

Версия, использующая reified T терпит неудачу, потому что она пытается де-сериализовать «1337» как Serializable , который является интерфейсом, поэтому он не может быть создан, и по умолчанию нет адаптера типа, который может де-сериализоваться в Serializable ( как и для List<...> ). Самый простой способ исправить это – передать List<String> в качестве аргумента типа.

В нереализованной версии фактическая информация о типе не передается вашей функции расширения. Вы можете проверить это, напечатав тип, который вы получаете из токена:

 fun <T> Gson.fromJson(json: String): T { val tt = object : TypeToken<T>() {}.type; println(tt); return fromJson(json, tt); } 

В неинициализированной версии, которая будет просто печатать T (т. Е. Нет актуальной информации о типе), но в обновленной версии она будет печатать фактический тип (+ любые модификаторы отклонения сайта сайта Kotlin, поэтому List<String> становится List<? extends String> ). (Я не знаю, почему Gson молча игнорирует эту ошибку отсутствия информации о типе).

Причина, по которой неработающая версия работает, является совпадением. Поскольку тип по умолчанию Gson для де-сериализации ["1337"] является ArrayList<String> и это также то, что вы получаете. Это просто назначается List , и поскольку generics удаляются, исключение исключений класса для несоответствия между String и Serializable в качестве аргумента типа. В конце концов, он работает в любом случае, поскольку String реализует Serializable .

Если вы слегка измените пример, где происходит различное действие, например, указав другой вид List , вы столкнулись с проблемой:

 val deser = fromJson<LinkedList<Serializable>>(ser) 

Выбрасывает java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.LinkedList

Для того чтобы иметь возможность передавать информацию о типе, вам нужно reified T , но это также означает, что сбой произойдет раньше и не останется незамеченным из-за стирания типа, например, в неориентированной версии.

Как заметил @Jorn Vernee, для сериализации / десериализации json требуется reified. Если reified не добавлен, он будет рассматриваться как List<Object> а конечный десериализованный объект будет иметь тип List<LinkedTreeMap> и вы получите ошибку при попытке использовать этот объект в качестве вашего интерфейса.

Для интерфейса serialize / deserialize вам необходимо зарегистрировать настраиваемый адаптер, чтобы добавить информацию о типе реализованного класса. Вот класс адаптера, в котором я ссылаюсь на этот сайт и вношу некоторые изменения.

 class InterfaceAdapter<T : Serializable> : JsonSerializer<T>, JsonDeserializer<T> { companion object { private val CLASSNAME = "CLASSNAME" private val DATA = "DATA" } @Throws(JsonParseException::class) override fun deserialize(jsonElement: JsonElement, type: Type, jsonDeserializationContext: JsonDeserializationContext): T { val jsonObject = jsonElement.asJsonObject val prim = jsonObject.get(CLASSNAME) as JsonPrimitive val className = prim.asString val klass = getObjectClass(className) return jsonDeserializationContext.deserialize(jsonObject.get(DATA), klass) } override fun serialize(jsonElement: T, type: Type, jsonSerializationContext: JsonSerializationContext): JsonElement { val jsonObject = JsonObject() jsonObject.addProperty(CLASSNAME, jsonElement.javaClass.name) jsonObject.add(DATA, jsonSerializationContext.serialize(jsonElement)) return jsonObject } private fun getObjectClass(className: String): Class<*> { try { return Class.forName(className) } catch (e: ClassNotFoundException) { throw JsonParseException(e.message) } } } 

Зарегистрируйте адаптер для объекта gson, а также укажите информацию о типе.

 inline fun <reified T> Any.toJson(): String { val builder = GsonBuilder() builder.registerTypeAdapter(Serializable::class.java, InterfaceAdapter<Serializable>()) val gson = builder.create() return gson.toJson(this, object : TypeToken<T>() {}.type) } inline fun <reified T> fromJson(json: String): T { val builder = GsonBuilder() builder.registerTypeAdapter(Serializable::class.java, InterfaceAdapter<Serializable>()) val gson = builder.create() return gson.fromJson<T>(json, object : TypeToken<T>() {}.type) } 

Пример кода:

 data class User(val name: String) : Serializable val data = listOf(User("Sam")) val ser = data.toJson<List<Serializable>>() val deser = fromJson<List<Serializable>>(ser) println(ser) println(deser.get(0)::class) println(deser.get(0)) /* Output [{"CLASSNAME":"User","DATA":{"name":"Sam"}}] class User User(name=Sam) */