Moshi: Modernizando a análise de JSON en Android

Para o desenvolvemento de calquera aplicación é necesario usar JSON para comunicarse co backend e, ata agora, o habitual era axudar a ser do GSON Biblioteca. Aínda que esta ferramenta funciona correctamente en Java, non é a mellor opción para Kotlin, porque poden aparecer erros incontrolados. Nos últimos tempos, desenvolvéronse outras ferramentas que permiten o mesmo, pero son máis seguras antes deste tipo de falla. Estamos falando de Jackson, Kotlinx.Serialization e Moshi. Aínda que estas tres opcións presentan as mesmas vantaxes sobre GSON, centrarémonos en Moshi por ser o máis popular e o máis estable agora.

Que é Moshi?

Moshi é unha librería para Pausa JSON en Java ou Kotlin Objects. É desenvolvido por Praza para case as mesmas persoas que GSON. Os principais desenvolvedores, Jesse Wilson e Jake Wharton, dixeron que Moshi podería considerarse gson v3.

As principais vantaxes sobre GSON son:

  • Moshi entende e funciona correctamente Tipos nulos de kotlin.
  • Xa non é necesario indicar a proguard que non ofrece o paquete onde ten os seus modelos situados, xa que coa configuración básica é suficiente.
  • Moshi en desenvolvemento e mellora, mentres que GSON é prácticamente unha librería rematada.
  • Ocupa menos tamaño no APK.

** (Jesse Wilson expón máis vantaxes aquí).

Se está acostumado a usar GSON, non terá problemas usando Moshi, xa que parecen moito. Non obstante, é necesario ter en conta algunhas diferenzas e tomar certas precaucións ao facer unha migración.

Usando a biblioteca

Recoméndase usar Moshi xuntos Codegen:

implementation "com.squareup.moshi:moshi:$moshi_version"kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"

e nunca esquece engadir as regras de proguard: https://github.com/square/moshi/blob/master/moshi/src/main/resources/ME. ..

Para usalo xunto con retrofit Use este conversor: https://github.com/square/retrofit/tree/master/retrofit-converters/moshi

implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"

e engada á súa instancia de adaptación do seguinte xeito:

Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(MoshiConverterFactory.create()) .build()

Con isto xa ten o seu proxecto listo para usar Moshi para deter o seu JSON.

Diferenzas con GSON: Modelos

Onde con GSON Tivemos:

data class FooDTO( val id: Int, @SerializedName("nombre") val name: String)

con moshi Temos:

@JsonClass(generateAdapter = true)data class FooDTO( val id: Int, @Json(name = "nombre") val name: String)

A anotación @SerializedName Cambia por @Json e porque usamos Codegen, é necesario Anota todas as clases con @JsonClass(generateAdapter = true.

di Feeers con GSON: deserializer / serializer

con GSON poñeríase deste xeito:

class DateAdapter : JsonDeserializer<Date>, JsonSerializer<Date> { private val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()) override fun serialize( src: Date, typeOfSrc: Type, context: JsonSerializationContext ): JsonElement { return JsonPrimitive(df.format(src)) } override fun deserialize( json: JsonElement, typeOfT: Type, context: JsonDeserializationContext ): Date { return df.parse(json.asString)!! }}

e con moshi este:

class DateAdapter { private val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()) @ToJson fun toJson(value: Date): String { return df.format(value) } @FromJson fun fromJson(source: String): Date { return df.parse(source)!! }}

Diferenzas con GSON: Kotlin Null Safety

GSON non entende os tipos non nulos de Kotlin. Isto significa que se tratamos de deserializar un valor nulo nunha tarifa non nulada, GSON fará que sen erros, que poden causar excepcións inesperadas con facilidade. Por exemplo:

Dado o modelo de comida anterior e un JSON {“ID”: 0, “Nome”: null}:

  • GSON creará o obxecto Foodo ( 0, null), aínda que isto non debería ser posible xa que o nome foi definido como non nulado. Non lanzará ningunha excepción, pero se obterá erros ao usar o nome:
    • name.dempty () producirá un nullpointerexception
    • name.trim () lanzará unha typeCastexception: null non pode ser Cast a tipo non nulo kotlin.Chosequence
  • Moshi lanzará a seguinte excepción ao deserializar:
    com.squareup.moshi.JsonDataException: Required value 'name' (JSON name 'nombre') missing

con Moshi saberemos desde o primeiro momento que falla na definición do noso modelo, con todo, con GSON non teremos coñecemento ata o momento de usar a variable e pode ocorrer en calquera parte do código e ser capaz de lanzar varios tipos de erro de acordo co uso da variable e todo isto ocorrerá mentres se lle pregunta como foi posible que unha variable non nula sexa nula.

migración de GSON

Porque Moshi é máis rigoroso non se recomenda facer unha migración masiva de todos os obxectos que ten en Java ou Kotlin xa que é posible que os seus modelos non estean ben definidos.

por Exemplo, se os seus modelos definiron variables non nulables que nalgún momento son nulas pero nunca foron utilizadas, con GSON nunca saltará o erro e isto estará oculto, pero se ese mesmo modelo migralo a Moshi o erro Irá a Moshi. Aínda así, hai varias opcións:

  • Se o seu modelo está en Java: convertelo en kotlin con todos os seus atributos nullibles.
  • Os seus modelos están en Kotlin e no código Isto úsalles tamén está en Kotlin: Eliminar atributos que non usan ou fan todos nulos.
  • Realizar unha migración progresiva: Comezar a usar Moshi na parse de novas solicitudes e deixar os antigos con GSON, pero tendo en conta que non deben ser mesturados dentro do mesmo modelo.
  • executar probas para que os seus modelos sexan 100% seguros de que están ben definidos e, polo tanto, migran sen medo a romper nada.

Migración progresiva

Por esta solución teremos un Anotación coa que imos dicir a Retrofit cales son as chamadas que queremos analizar con Moshi.

@Target(AnnotationTarget.FUNCTION)@Retention(RUNTIME)annotation class Moshiinterface GdaxApi { @GET("products") fun products(): List<Product> @Moshi @GET("products") fun productsMoshi(): List<Product>}

Para que isto funcione, temos que crear o noso propio conversor. fábrica para comprobar se un servizo é marcado con @moshi:

class MoshiMigrationConverter(private val moshiConverterFactory: MoshiConverterFactory) : Converter.Factory() { override fun responseBodyConverter( type: Type, annotations: Array<Annotation>, retrofit: Retrofit): Converter<ResponseBody, *>? { for (annotation in annotations) { if (annotation.annotationClass == Moshi::class) { return moshiConverterFactory.responseBodyConverter(type, annotations, retrofit) } } return null } override fun requestBodyConverter( type: Type, parameterAnnotations: Array<Annotation>, methodAnnotations: Array<Annotation>, retrofit: Retrofit): Converter<*, RequestBody>? { for (annotation in methodAnnotations) { if (annotation.annotationClass == Moshi::class) { return moshiConverterFactory.requestBodyConverter( type, parameterAnnotations, methodAnnotations, retrofit) } } return null }}

Finalmente a inserimos na nosa instancia de retrofit:

val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(MoshiMigrationConverter(MoshiConverterFactory.create())) .addConverterFactory(GsonConverterFactory.create()) .build()

Deste xeito, os novos servizos que está a implementar nas súas aplicacións poden usar Moshi e os antigos continúan usando GSON e tamén permite migrar un por un Ervicios.

Migración con probas

O primeiro paso sería facer unha proba para verificar que a definición do noso modelo corresponde ao que devolve o servidor. Para iso só necesitaremos junit e un ficheiro .json que é a copia do que o servizo volve a nós. Imos poñer o noso modelo de comida anterior e engadir o seguinte “foo.json” dentro do cartafol de recursos de proba, estaría en “proba / recursos / foo.json”

A nosa clase de proba sería así:

class FooDTOTest { private val loader = javaClass.classLoader!! private val gson = GsonBuilder().create() @Test fun parse() { val jsonString = String(loader.getResourceAsStream("foo.json").readBytes()) val actual = gson.fromJson(jsonString, FooDTO::class.java) val expected = FooDTO(10, "SDOS") assertEquals(expected, actual) }}

A variable GSON debe ser o mesmo que usalo en retrofit, no caso de non Ten número gonsbuilder (). Crear () é o que está creado por defecto.

O que estamos facendo é ler o ficheiro “foo.json”, contámoslles que a gon se deserializa en alimentos e o resultado Na variable actual. Na esperada creamos un obxectivo que esperamos que sexa o resultado de deserializar o JSON e finalmente comprobamos con Junit que ambos os modelos son iguais.

Unha vez que temos as nosas probas, xa podemos migrar comida para usalo con Moshi eo noso tipo de proba sería así:

class FooDTOTest { private val loader = javaClass.classLoader!! private val moshi = Moshi.Builder().build() @Test fun parse() { val jsonString = String(loader.getResourceAsStream("foo.json").readBytes()) val actual = moshi.adapter(FooDTO::class.java).fromJson(jsonString) val expected = FooDTO(10, "SDOS") assertEquals(expected, actual) }}

Como antes, Moshi debe ser o mesmo que usar en retrofit e moshi.builder (). Construír () é o que é creado por defecto. A idea da proba é aínda a mesma, pero agora usamos Moshi para deserializar o JSON.

Polo tanto, máis probas con diferentes tipos de respostas do seu servidor que ten mellor e probablemente poida descubrir Erros nos seus modelos antes de migrar a Moshi.

Conclusións

A chegada de Kotlin deixou probas de deficiencias GSON, que poden resolverse usando novas librerías, como Moshi. Moshi é unha seguridade nula, polo que obriga a ter modelos ben definidos, evitando así a aparición de erros inesperados. Utilízase de forma similar a GSON, pero evita engadir excepcións a proguard. Ademais, pode coexistir con GSON e converterse nunha migración progresiva.

GSON foi un bo compañeiro de viaxe pero é hora de usar outras alternativas. Agora tes as ferramentas básicas para probar Moshi e comprobar as súas vantaxes por vós mesmos.

Esperamos que fose interesante a nosa publicación sobre Moshi. Vas a usar ou prefires outras librerías? Déixanos un comentario coa túa experiencia ou opinión sobre iso.

Deixa unha resposta

O teu enderezo electrónico non se publicará Os campos obrigatorios están marcados con *