refactor: migrate Gson serialization to kotlinx.serialization (#475)

* refactor: migrate Gson serialization to kotlinx.serialization

* fix(dto): resolve serializing Any? and Map<String, Any?> with JsonElement

* test(refactor): fix unit test issues under kotlinx.serialization and update nullable DTO defaults

* fix(chat): support flexible numeric and string decoding for message timestamps

* fix(chat): sequentialize cache loading to avoid overwriting network state
This commit is contained in:
M57 authored and GitHub committed 2026-07-05 21:30:44 +03:00
1 parent 1be5ab2f6d
commit 5697405b19
64 files changed
+863 -577

No files matched your search

@@ -4,9 +4,10 @@ import com.m57.hermescontrol.BuildConfig
import com.m57.hermescontrol.data.local.AuthManager
import okhttp3.CertificatePinner
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.kotlinx.serialization.asConverterFactory
/**
* Provides a Retrofit-backed [HermesApiService].
@@ -71,7 +72,7 @@ object ApiClient {
.Builder()
.baseUrl("http://$host:$port/")
.client(tempOkHttp)
.addConverterFactory(GsonConverterFactory.create(OkHttpProvider.gson))
.addConverterFactory(OkHttpProvider.json.asConverterFactory("application/json".toMediaType()))
.build()
return tempRetrofit.create(HermesApiService::class.java)
@@ -135,7 +136,7 @@ object ApiClient {
.Builder()
.baseUrl(AuthManager.baseUrl())
.client(okHttp)
.addConverterFactory(GsonConverterFactory.create(OkHttpProvider.gson))
.addConverterFactory(OkHttpProvider.json.asConverterFactory("application/json".toMediaType()))
.build()
.also { retrofit = it }
@@ -1,6 +1,5 @@
package com.m57.hermescontrol.data.remote
import com.google.gson.JsonElement
import com.m57.hermescontrol.data.model.AchievementsResponse
import com.m57.hermescontrol.data.model.ActionResponse
import com.m57.hermescontrol.data.model.ActionStatusResponse
@@ -91,6 +90,7 @@ import com.m57.hermescontrol.data.model.WebhookSubscription
import com.m57.hermescontrol.data.model.WebhookToggleSubscriptionRequest
import com.m57.hermescontrol.data.model.WebhooksResponse
import com.m57.hermescontrol.data.model.WebhooksToggleRequest
import kotlinx.serialization.json.JsonElement
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.http.Body
@@ -1,7 +1,8 @@
package com.m57.hermescontrol.data.remote
import com.google.gson.JsonParser
import kotlinx.coroutines.delay
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import retrofit2.Response
import java.io.IOException
@@ -49,11 +50,8 @@ fun mapHttpError(
val detail =
if (errorBody != null) {
try {
val parsed =
com.google.gson.JsonParser
.parseString(errorBody)
.asJsonObject
parsed.get("detail")?.asString
val parsed = OkHttpProvider.json.parseToJsonElement(errorBody).jsonObject
parsed["detail"]?.jsonPrimitive?.content
} catch (_: Exception) {
null
}
@@ -1,7 +1,8 @@
package com.m57.hermescontrol.data.remote
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
import okhttp3.ConnectionPool
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
@@ -42,6 +43,10 @@ object OkHttpProvider {
.followRedirects(false)
.build()
// Shared Gson — thread-safe, zero-config
val gson: Gson = GsonBuilder().create()
@OptIn(ExperimentalSerializationApi::class)
val json: Json =
Json {
ignoreUnknownKeys = true
namingStrategy = JsonNamingStrategy.SnakeCase
}
}