Replace gson with kotlin serialization

pull/2883/head
Alex Baker 6 months ago
parent d556863fda
commit ebe5e5c009

@ -219,7 +219,6 @@ dependencies {
implementation(libs.kotlinx.serialization)
implementation(libs.okhttp)
implementation(libs.persistent.cookiejar)
implementation(libs.gson)
implementation(libs.material)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.constraintlayout)

@ -1,16 +1,23 @@
package org.tasks.billing
import com.android.billingclient.api.Purchase
import com.google.gson.GsonBuilder
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.tasks.billing.BillingClientImpl.Companion.STATE_PURCHASED
import java.util.regex.Pattern
class Purchase(private val purchase: Purchase) {
constructor(json: String?) : this(GsonBuilder().create().fromJson<Purchase>(json, Purchase::class.java))
constructor(json: String) : this(
Json.parseToJsonElement(json).jsonObject.let {
Purchase(it["zza"]!!.jsonPrimitive.content, it["zzb"]!!.jsonPrimitive.content)
}
)
fun toJson(): String {
return GsonBuilder().create().toJson(purchase)
return Json.encodeToString(mapOf("zza" to purchase.originalJson, "zzb" to purchase.signature))
}
override fun toString(): String {

@ -1,9 +0,0 @@
package org.tasks.extensions
import com.google.gson.JsonObject
object JsonObject {
fun JsonObject.getStringOrNull(key: String): String? = getOrNull(key)?.asString
fun JsonObject.getOrNull(key: String) = if (has(key)) get(key) else null
}

@ -1,18 +1,20 @@
package org.tasks.location
import android.content.Context
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.double
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.OkHttpClient
import okhttp3.Request
import org.tasks.DebugNetworkInterceptor
import org.tasks.R
import org.tasks.data.entity.Place
import org.tasks.extensions.JsonObject.getStringOrNull
import org.tasks.preferences.Preferences
import java.io.IOException
import javax.inject.Inject
@ -42,33 +44,35 @@ class GeocoderMapbox @Inject constructor(
companion object {
internal fun jsonToPlace(json: String): Place? =
JsonParser
.parseString(json).asJsonObject.getAsJsonArray("features")
.takeIf { it.size() > 0 }?.get(0)?.asJsonObject
?.let { toPlace(it) }
Json.parseToJsonElement(json)
.jsonObject["features"]
?.jsonArray
?.firstOrNull()
?.toPlace()
internal fun toPlace(feature: JsonObject): Place {
val text = feature.get("text").asString
val coords = feature.get("center").asCoordinates
internal fun JsonElement.toPlace(): Place {
val text = jsonObject["text"]!!.jsonPrimitive.content
val coords = jsonObject["center"]!!.asCoordinates
return Place(
name = if (feature.get("place_type").asStringList.contains("address")) {
feature
.getStringOrNull("address")
name = if (jsonObject["place_type"]!!.asStringList.contains("address")) {
jsonObject["address"]
?.jsonPrimitive
?.content
?.let { "$it $text" }
?: text
} else {
text
},
address = feature.get("place_name").asString,
address = jsonObject["place_name"]!!.jsonPrimitive.content,
longitude = coords.first,
latitude = coords.second,
)
}
private val JsonElement.asStringList: List<String>
get() = asJsonArray.map { it.asString }
get() = jsonArray.map { it.jsonPrimitive.content }
private val JsonElement.asCoordinates: Pair<Double, Double>
get() = asJsonArray.let { Pair(it[0].asDouble, it[1].asDouble) }
val JsonElement.asCoordinates: Pair<Double, Double>
get() = jsonArray.let { Pair(it[0].jsonPrimitive.double, it[1].jsonPrimitive.double) }
}
}
}

@ -1,19 +1,20 @@
package org.tasks.location
import android.content.Context
import com.google.gson.JsonElement
import com.google.gson.JsonParser
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Request
import org.tasks.BuildConfig
import org.tasks.R
import org.tasks.data.entity.Place
import org.tasks.extensions.JsonObject.getOrNull
import org.tasks.extensions.JsonObject.getStringOrNull
import org.tasks.http.HttpClientFactory
import org.tasks.http.HttpException
import org.tasks.location.GeocoderMapbox.Companion.asCoordinates
import javax.inject.Inject
class GeocoderNominatim @Inject constructor(
@ -41,26 +42,24 @@ class GeocoderNominatim @Inject constructor(
private const val UA_VALUE = "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}-${BuildConfig.BUILD_TYPE})"
internal fun jsonToPlace(json: String): Place? =
JsonParser
.parseString(json).asJsonObject.getAsJsonArray("features")
.takeIf { it.size() > 0 }?.get(0)?.asJsonObject
?.let { feature ->
Json.parseToJsonElement(json)
.jsonObject["features"]
?.jsonArray
?.firstOrNull()
?.let { feature ->
val geocoding = feature
.get("properties").asJsonObject
.get("geocoding").asJsonObject
val geometry = feature.get("geometry").asJsonObject
val coords = geometry.get("coordinates").asCoordinates
.jsonObject["properties"]!!
.jsonObject["geocoding"]!!
val geometry = feature.jsonObject["geometry"]!!
val coords = geometry.jsonObject["coordinates"]!!.asCoordinates
return Place(
name = geocoding.getStringOrNull("name")
?: geocoding.getStringOrNull("housenumber")
?.let { "$it ${geocoding.get("street").asString}" },
address = geocoding.getOrNull("label")?.asString,
name = geocoding.jsonObject["name"]?.jsonPrimitive?.content
?: geocoding.jsonObject["housenumber"]?.jsonPrimitive?.content
?.let { "$it ${geocoding.jsonObject["street"]?.jsonPrimitive?.content}" },
address = geocoding.jsonObject["label"]?.jsonPrimitive?.content,
longitude = coords.first,
latitude = coords.second,
)
}
private val JsonElement.asCoordinates: Pair<Double, Double>
get() = asJsonArray.let { Pair(it[0].asDouble, it[1].asDouble) }
}
}

@ -2,15 +2,19 @@ package org.tasks.location
import android.content.Context
import android.os.Bundle
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.double
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Request
import org.tasks.R
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_TASKS
import org.tasks.data.dao.CaldavDao
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_TASKS
import org.tasks.data.entity.Place
import org.tasks.http.HttpClientFactory
import org.tasks.http.HttpException
@ -93,52 +97,45 @@ class PlaceSearchGoogle @Inject constructor(
"international_phone_number"
).joinToString(",")
internal fun String.toJson(): JsonObject = JsonParser.parseString(this).asJsonObject
internal fun String.toJson(): JsonObject = Json.parseToJsonElement(this).jsonObject
private fun checkResult(json: JsonObject) {
val status = json.get("status").asString
val status = json["status"]?.jsonPrimitive?.content
when {
status == "OK" -> return
json.has("error_message") ->
throw IllegalStateException(json.get("error_message").asString)
else ->
throw IllegalStateException(status)
else -> throw IllegalStateException(
json["error_message"]?.jsonPrimitive?.content ?: status
)
}
}
internal fun toSearchResults(json: JsonObject): List<PlaceSearchResult> =
json.get("predictions")
.asJsonArray
.map { it.asJsonObject }
.filter { it.has("place_id") }
json["predictions"]!!
.jsonArray
.map { it.jsonObject }
.filter { it.contains("place_id") }
.map { toSearchEntry(it) }
private fun toSearchEntry(json: JsonObject): PlaceSearchResult {
val place = json.get("structured_formatting").asJsonObject
val place = json["structured_formatting"]!!.jsonObject
return PlaceSearchResult(
json.get("place_id").asString,
place.get("main_text").asString,
place.get("secondary_text").asString
json["place_id"]!!.jsonPrimitive.content,
place["main_text"]!!.jsonPrimitive.content,
place["secondary_text"]!!.jsonPrimitive.content,
)
}
internal fun toPlace(json: JsonObject): Place {
val result = json.get("result").asJsonObject
val location = result.get("geometry").asJsonObject.get("location").asJsonObject
val result = json["result"]!!.jsonObject
val location = result["geometry"]!!.jsonObject["location"]!!.jsonObject
return Place(
name = result.get("name").asString,
address = result.getString("formatted_address"),
phone = result.getString("international_phone_number"),
url = result.getString("website"),
latitude = location.get("lat").asDouble,
longitude = location.get("lng").asDouble,
name = result["name"]!!.jsonPrimitive.content,
address = result["formatted_address"]?.jsonPrimitive?.content,
phone = result["international_phone_number"]?.jsonPrimitive?.content,
url = result["website"]?.jsonPrimitive?.content,
latitude = location["lat"]!!.jsonPrimitive.double,
longitude = location["lng"]!!.jsonPrimitive.double,
)
}
private fun JsonObject.getString(field: String): String? = if (has(field)) {
get(field).asString
} else {
null
}
}
}

@ -2,10 +2,13 @@ package org.tasks.location
import android.content.Context
import android.os.Bundle
import com.google.gson.JsonParser
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.OkHttpClient
import okhttp3.Request
import org.tasks.DebugNetworkInterceptor
@ -53,17 +56,18 @@ class PlaceSearchMapbox @Inject constructor(
companion object {
internal fun jsonToSearchResults(json: String): List<PlaceSearchResult> =
JsonParser
.parseString(json).asJsonObject.getAsJsonArray("features")
.map { it.asJsonObject }
.map {
val place = toPlace(it)
PlaceSearchResult(
it.get("id").asString,
place.name,
place.displayAddress,
place
)
}
Json.parseToJsonElement(json)
.jsonObject["features"]!!
.jsonArray
.map { it.jsonObject }
.map {
val place = it.toPlace()
PlaceSearchResult(
it["id"]!!.jsonPrimitive.content,
place.name,
place.displayAddress,
place
)
}
}
}

@ -1,11 +1,14 @@
package org.tasks.sync.microsoft
import com.google.gson.Gson
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import retrofit2.Response
@Serializable
data class Error(
val error: ErrorBody,
) {
@Serializable
data class ErrorBody(
val code: String,
val message: String,
@ -13,6 +16,6 @@ data class Error(
companion object {
fun <T> Response<T>.toMicrosoftError()
= errorBody()?.string()?.let { Gson().fromJson(it, Error::class.java) }
= errorBody()?.string()?.let { Json.decodeFromString<Error>(it) }
}
}

@ -2,17 +2,18 @@ package org.tasks.sync.microsoft
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import com.google.gson.Gson
import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.RequestBody.Companion.toRequestBody
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
import org.tasks.data.dao.CaldavDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.dao.CaldavDao
import org.tasks.http.HttpClientFactory
import org.tasks.http.HttpClientFactory.Companion.MEDIA_TYPE_JSON
import retrofit2.HttpException
@ -46,7 +47,7 @@ class MicrosoftListSettingsActivityViewModel @Inject constructor(
_viewState.update { it.copy(requestInFlight = true) }
val microsoftService = httpClientFactory.getMicrosoftService(account)
val taskList = TaskLists.TaskList(displayName = displayName)
val body = Gson().toJson(taskList).toRequestBody(MEDIA_TYPE_JSON)
val body = Json.encodeToString(taskList).toRequestBody(MEDIA_TYPE_JSON)
val result = microsoftService.createList(body)
if (result.isSuccessful) {
val list = CaldavCalendar(
@ -77,7 +78,7 @@ class MicrosoftListSettingsActivityViewModel @Inject constructor(
_viewState.update { it.copy(requestInFlight = true) }
val microsoftService = httpClientFactory.getMicrosoftService(account)
val taskList = TaskLists.TaskList(displayName = displayName)
val body = Gson().toJson(taskList).toRequestBody(MEDIA_TYPE_JSON)
val body = Json.encodeToString(taskList).toRequestBody(MEDIA_TYPE_JSON)
val result = microsoftService.updateList(list?.uuid!!, body)
if (result.isSuccessful) {
result.body()?.applyTo(list)

@ -1,6 +1,7 @@
package org.tasks.sync.microsoft
import com.squareup.moshi.Json
import kotlinx.serialization.Serializable
import org.tasks.data.entity.CaldavCalendar
data class TaskLists(
@ -8,6 +9,7 @@ data class TaskLists(
val value: List<TaskList>,
@field:Json(name = "@odata.nextLink") val nextPage: String?,
) {
@Serializable
data class TaskList(
@Json(name = "@odata.etag") val etag: String? = null,
val displayName: String? = null,

@ -619,7 +619,6 @@
++--- com.squareup.okhttp3:okhttp:4.12.0 (*)
++--- com.github.franmontiel:PersistentCookieJar:1.0.1
+| \--- com.squareup.okhttp3:okhttp:3.1.2 -> 4.12.0 (*)
++--- com.google.code.gson:gson:2.10.1
++--- com.google.android.material:material:1.12.0 (*)
++--- androidx.compose.material3:material3 -> 1.2.1
+| \--- androidx.compose.material3:material3-android:1.2.1

@ -848,7 +848,6 @@
++--- com.squareup.okhttp3:okhttp:4.12.0 (*)
++--- com.github.franmontiel:PersistentCookieJar:1.0.1
+| \--- com.squareup.okhttp3:okhttp:3.1.2 -> 4.12.0 (*)
++--- com.google.code.gson:gson:2.10.1
++--- com.google.android.material:material:1.12.0 (*)
++--- androidx.compose.material3:material3 -> 1.2.1
+| \--- androidx.compose.material3:material3-android:1.2.1

@ -24,7 +24,6 @@ google-api-drive = "v3-rev20240327-2.0.0"
google-api-tasks = "v1-rev20240423-2.0.0"
google-services = "4.4.1"
gradle = "8.4.0"
gson = "2.10.1"
hilt = "1.2.0"
ical4android = "12fe73a"
jchronic = "0.2.6"
@ -123,7 +122,6 @@ google-api-drive = { module = "com.google.apis:google-api-services-drive", versi
google-api-tasks = { module = "com.google.apis:google-api-services-tasks", version.ref = "google-api-tasks" }
google-services = { module = "com.google.gms:google-services", version.ref = "google-services" }
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
jchronic = { module = "com.rubiconproject.oss:jchronic", version.ref = "jchronic" }
junit = { module = "junit:junit", version.ref = "junit-junit" }
kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }

Loading…
Cancel
Save