From bb16b1f945fc10f552018cd74954a79043c84ba6 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara <9083456+MohamadJaara@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:33:28 +0200 Subject: [PATCH 1/2] refactor: replace Gson with Kotlin serialization for data models --- benchmark/build.gradle.kts | 3 +- gradle/libs.versions.toml | 2 - tests/testsCore/build.gradle.kts | 3 +- tests/testsSupport/build.gradle.kts | 3 +- .../src/main/call/CallingService.kt | 38 +++++++++++-------- .../src/main/call/models/BackendDTO.kt | 2 + .../testsSupport/src/main/call/models/Call.kt | 3 ++ .../src/main/call/models/CallFlow.kt | 24 ++++++------ .../src/main/call/models/CallRequest.kt | 8 ++-- .../src/main/call/models/CallStatus.kt | 3 ++ .../src/main/call/models/Instance.kt | 25 ++++++------ .../src/main/call/models/InstanceRequest.kt | 3 ++ .../src/main/call/models/InstanceStatus.kt | 3 ++ .../src/main/call/models/Metrics.kt | 13 ++++--- .../main/call/models/VersionedInstanceType.kt | 7 +++- 15 files changed, 87 insertions(+), 53 deletions(-) diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts index 8f140925b26..cf4a29178e9 100644 --- a/benchmark/build.gradle.kts +++ b/benchmark/build.gradle.kts @@ -21,6 +21,7 @@ import flavor.ProductFlavors plugins { id("com.android.test") alias(libs.plugins.androidx.baselineprofile) + alias(libs.plugins.kotlin.serialization) } android { @@ -81,7 +82,7 @@ dependencies { implementation(libs.androidx.test.uiAutomator) implementation(libs.androidx.benchmark.macro.junit4) implementation(libs.datafaker) - implementation(libs.gson) + implementation(libs.ktx.serialization) implementation(libs.junit4) implementation(libs.zxing.android.embedded) implementation(libs.zxing.core) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fd88b1be82f..00bf081f4fd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -116,7 +116,6 @@ hamcrest = "3.0" rules = "1.7.0" zxing = "3.5.2" zxingAndroid = "4.3.0" -gson = "2.13.2" allureKotlin = "2.4.0" androidx-test-services = "1.6.0" @@ -303,7 +302,6 @@ androidx-test-work = { module = "androidx.work:work-testing", version.ref = "and androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmark-macro-junit4" } zxing-core = { module = "com.google.zxing:core", version.ref = "zxing" } zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxingAndroid" } -gson = { module = "com.google.code.gson:gson", version.ref = "gson" } # Test androidx-espresso-accessibility = { module = "androidx.test.espresso:espresso-accessibility", version.ref = "androidx-espresso" } diff --git a/tests/testsCore/build.gradle.kts b/tests/testsCore/build.gradle.kts index 406f6af537e..de228a44fad 100644 --- a/tests/testsCore/build.gradle.kts +++ b/tests/testsCore/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id(libs.plugins.wire.android.test.library.get().pluginId) + alias(libs.plugins.kotlin.serialization) id(libs.plugins.wire.compose.compiler.get().pluginId) } @@ -36,7 +37,7 @@ dependencies { implementation(libs.datafaker) androidTestImplementation(libs.zxing.core) androidTestImplementation(libs.zxing.android.embedded) - androidTestImplementation(libs.gson) + androidTestImplementation(libs.ktx.serialization) androidTestImplementation(libs.allure.kotlin.android) androidTestUtil(libs.androidx.test.orchestrator) androidTestUtil(libs.androidx.test.services) diff --git a/tests/testsSupport/build.gradle.kts b/tests/testsSupport/build.gradle.kts index 1bc38c3f157..ff825cdb44f 100644 --- a/tests/testsSupport/build.gradle.kts +++ b/tests/testsSupport/build.gradle.kts @@ -5,6 +5,7 @@ import java.util.Properties // Apply your test library plugin plugins { id(libs.plugins.wire.android.test.library.get().pluginId) + alias(libs.plugins.kotlin.serialization) id(libs.plugins.wire.compose.compiler.get().pluginId) } @@ -83,7 +84,7 @@ dependencies { androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.test.uiAutomator) implementation(libs.datafaker) - androidTestImplementation(libs.gson) + implementation(libs.ktx.serialization) } // Register a custom Gradle task 'fetchSecrets' to fetch secrets from 1Password CLI and generate secrets.json diff --git a/tests/testsSupport/src/main/call/CallingService.kt b/tests/testsSupport/src/main/call/CallingService.kt index 77c40a83d4d..71a67f93c49 100644 --- a/tests/testsSupport/src/main/call/CallingService.kt +++ b/tests/testsSupport/src/main/call/CallingService.kt @@ -17,9 +17,11 @@ import call.models.CallFlow import call.models.CallRequest import call.models.Instance import call.models.InstanceRequest -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken import com.wire.android.testSupport.BuildConfig +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import network.HttpRequestException import java.io.IOException import java.net.HttpCookie @@ -37,7 +39,13 @@ class CallingService( val applicationJson = "application/json" val contentTypeString = "Content-Type" - private val gson = Gson() + @OptIn(ExperimentalSerializationApi::class) + private val json = Json { + coerceInputValues = true + encodeDefaults = true + explicitNulls = false + ignoreUnknownKeys = true + } private val cookies = ConcurrentHashMap() private var basicAuthUser: String? = null private var basicAuthPassword: String? = null @@ -100,8 +108,8 @@ class CallingService( conn.setRequestProperty(contentTypeString, applicationJson) conn.setRequestProperty("Accept", applicationJson) - val response = httpPost(conn, gson.toJson(request), intArrayOf(HttpURLConnection.HTTP_OK)) - val instance = gson.fromJson(response, Instance::class.java) + val response = httpPost(conn, json.encodeToString(request), intArrayOf(HttpURLConnection.HTTP_OK)) + val instance = json.decodeFromString(response) conn.getHeaderField("Set-Cookie")?.let { cookieHeader -> HttpCookie.parse(cookieHeader) @@ -120,7 +128,7 @@ class CallingService( return try { val response = httpPut(conn, "", intArrayOf(HttpURLConnection.HTTP_OK)) cookies.remove(instance.id) - gson.fromJson(response, Instance::class.java) + json.decodeFromString(response) } catch (ex: Exception) { throw HttpRequestException("destroyInstance failed: ${ex.message}", 500) } @@ -131,7 +139,7 @@ class CallingService( val conn = buildRequestForInstance(instance.id, target) return try { val response = httpGet(conn, intArrayOf(HttpURLConnection.HTTP_OK)) - gson.fromJson(response, Instance::class.java) + json.decodeFromString(response) } catch (ex: Exception) { throw HttpRequestException("getInstance failed: ${ex.message}", 500) } @@ -142,8 +150,7 @@ class CallingService( val conn = buildRequestForInstance(instance.id, target) return try { val response = httpGet(conn, intArrayOf(HttpURLConnection.HTTP_OK)) - val type = object : TypeToken>() {}.type - gson.fromJson(response, type) + json.decodeFromString>(response) } catch (ex: Exception) { throw HttpRequestException("getFlows failed: ${ex.message}", 500) } @@ -190,8 +197,7 @@ class CallingService( val conn = buildRequestForCertainInstance(serverId, target) try { val response = httpGet(conn, intArrayOf(HttpURLConnection.HTTP_OK)) - val type = object : TypeToken>() {}.type - val serverInstances: List = gson.fromJson(response, type) + val serverInstances = json.decodeFromString>(response) serverInstances.forEach { if (!cookies.containsKey(it.id)) { cookies[it.id!!] = HttpCookie("SERVERID", serverId) @@ -273,22 +279,22 @@ class CallingService( // region CALL HELPERS - private fun performCall(instance: Instance, path: String, request: Any): Call { + private fun performCall(instance: Instance, path: String, request: CallRequest): Call { val conn = buildRequestForInstance(instance.id, "/api/v1/instance/${instance.id}$path") - val response = httpPost(conn, gson.toJson(request), intArrayOf(HttpURLConnection.HTTP_OK)) - return gson.fromJson(response, Call::class.java) + val response = httpPost(conn, json.encodeToString(request), intArrayOf(HttpURLConnection.HTTP_OK)) + return json.decodeFromString(response) } private fun performCallPut(instance: Instance, path: String): Call { val conn = buildRequestForInstance(instance.id, "/api/v1/instance/${instance.id}$path") val response = httpPut(conn, "", intArrayOf(HttpURLConnection.HTTP_OK)) - return gson.fromJson(response, Call::class.java) + return json.decodeFromString(response) } private fun performCallGet(instance: Instance, path: String): Call { val conn = buildRequestForInstance(instance.id, "/api/v1/instance/${instance.id}$path") val response = httpGet(conn, intArrayOf(HttpURLConnection.HTTP_OK)) - return gson.fromJson(response, Call::class.java) + return json.decodeFromString(response) } // endregion diff --git a/tests/testsSupport/src/main/call/models/BackendDTO.kt b/tests/testsSupport/src/main/call/models/BackendDTO.kt index 4d4c32e5a65..4f3331af79a 100644 --- a/tests/testsSupport/src/main/call/models/BackendDTO.kt +++ b/tests/testsSupport/src/main/call/models/BackendDTO.kt @@ -18,7 +18,9 @@ package call.models import backendUtils.BackendClient +import kotlinx.serialization.Serializable +@Serializable data class BackendDTO( var name: String, val webappUrl: String, diff --git a/tests/testsSupport/src/main/call/models/Call.kt b/tests/testsSupport/src/main/call/models/Call.kt index d194fb5f9a2..038c7534c52 100644 --- a/tests/testsSupport/src/main/call/models/Call.kt +++ b/tests/testsSupport/src/main/call/models/Call.kt @@ -17,6 +17,9 @@ */ package call.models +import kotlinx.serialization.Serializable + +@Serializable data class Call( var id: String? = null, var status: CallStatus? = null, diff --git a/tests/testsSupport/src/main/call/models/CallFlow.kt b/tests/testsSupport/src/main/call/models/CallFlow.kt index 06f5849e4c1..f149c1645cd 100644 --- a/tests/testsSupport/src/main/call/models/CallFlow.kt +++ b/tests/testsSupport/src/main/call/models/CallFlow.kt @@ -17,23 +17,25 @@ */ package call.models -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +@Serializable data class CallFlow( - @SerializedName("audioPacketsReceived") - val audioPacketsReceived: Long, + @SerialName("audioPacketsReceived") + val audioPacketsReceived: Long = 0, - @SerializedName("audioPacketsSent") - val audioPacketsSent: Long, + @SerialName("audioPacketsSent") + val audioPacketsSent: Long = 0, - @SerializedName("videoPacketsReceived") - val videoPacketsReceived: Long, + @SerialName("videoPacketsReceived") + val videoPacketsReceived: Long = 0, - @SerializedName("videoPacketsSent") - val videoPacketsSent: Long, + @SerialName("videoPacketsSent") + val videoPacketsSent: Long = 0, - @SerializedName("remoteUserId") - val remoteUserId: String + @SerialName("remoteUserId") + val remoteUserId: String = "" ) { // Secondary constructor for parsing from raw pcStats string diff --git a/tests/testsSupport/src/main/call/models/CallRequest.kt b/tests/testsSupport/src/main/call/models/CallRequest.kt index 4bfc26f62b8..8c8a7c14fca 100644 --- a/tests/testsSupport/src/main/call/models/CallRequest.kt +++ b/tests/testsSupport/src/main/call/models/CallRequest.kt @@ -17,13 +17,15 @@ */ package call.models -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +@Serializable data class CallRequest( - @SerializedName("conversationId") + @SerialName("conversationId") val conversationId: String = "", - @SerializedName("timeout") + @SerialName("timeout") val timeout: Long = DEFAULT_CALL_TIMEOUT ) { companion object { diff --git a/tests/testsSupport/src/main/call/models/CallStatus.kt b/tests/testsSupport/src/main/call/models/CallStatus.kt index 3b05fb88d1d..09999d78e7e 100644 --- a/tests/testsSupport/src/main/call/models/CallStatus.kt +++ b/tests/testsSupport/src/main/call/models/CallStatus.kt @@ -17,6 +17,9 @@ */ package call.models +import kotlinx.serialization.Serializable + +@Serializable enum class CallStatus { NON_EXISTENT, WAITING, diff --git a/tests/testsSupport/src/main/call/models/Instance.kt b/tests/testsSupport/src/main/call/models/Instance.kt index e70d45c713e..092fc2b5c00 100644 --- a/tests/testsSupport/src/main/call/models/Instance.kt +++ b/tests/testsSupport/src/main/call/models/Instance.kt @@ -17,18 +17,21 @@ */ package call.models +import kotlinx.serialization.Serializable + +@Serializable data class Instance( - val id: String?, - val instanceStatus: InstanceStatus?, - val currentCall: Call?, - val email: String?, - val password: String?, - val backend: String?, - val screenshot: String?, - val instanceType: VersionedInstanceType?, - val name: String?, - val timeout: Long?, - val created: Long?, + val id: String? = null, + val instanceStatus: InstanceStatus? = null, + val currentCall: Call? = null, + val email: String? = null, + val password: String? = null, + val backend: String? = null, + val screenshot: String? = null, + val instanceType: VersionedInstanceType? = null, + val name: String? = null, + val timeout: Long? = null, + val created: Long? = null, val message: String? = null ) { override fun toString(): String { diff --git a/tests/testsSupport/src/main/call/models/InstanceRequest.kt b/tests/testsSupport/src/main/call/models/InstanceRequest.kt index edc1ce0aa62..7025656f9b3 100644 --- a/tests/testsSupport/src/main/call/models/InstanceRequest.kt +++ b/tests/testsSupport/src/main/call/models/InstanceRequest.kt @@ -17,6 +17,9 @@ */ package call.models +import kotlinx.serialization.Serializable + +@Serializable data class InstanceRequest( val email: String, val password: String, diff --git a/tests/testsSupport/src/main/call/models/InstanceStatus.kt b/tests/testsSupport/src/main/call/models/InstanceStatus.kt index d969957bb74..ffeb56ad30c 100644 --- a/tests/testsSupport/src/main/call/models/InstanceStatus.kt +++ b/tests/testsSupport/src/main/call/models/InstanceStatus.kt @@ -18,6 +18,9 @@ package call.models +import kotlinx.serialization.Serializable + +@Serializable enum class InstanceStatus { NON_EXISTENT, STARTING, diff --git a/tests/testsSupport/src/main/call/models/Metrics.kt b/tests/testsSupport/src/main/call/models/Metrics.kt index 790612af581..4a0409f1897 100644 --- a/tests/testsSupport/src/main/call/models/Metrics.kt +++ b/tests/testsSupport/src/main/call/models/Metrics.kt @@ -20,12 +20,15 @@ ) package call.models +import kotlinx.serialization.Serializable + +@Serializable data class Metrics( - val success: Boolean, - val estabTimeMs: Long, - val setupTimeMs: Long, - val avgRateU: Long, - val avgRateD: Long + val success: Boolean = false, + val estabTimeMs: Long = 0, + val setupTimeMs: Long = 0, + val avgRateU: Long = 0, + val avgRateD: Long = 0 ) { override fun toString(): String { return "Metrics(success=$success, estab_time_ms=$estabTimeMs, " + diff --git a/tests/testsSupport/src/main/call/models/VersionedInstanceType.kt b/tests/testsSupport/src/main/call/models/VersionedInstanceType.kt index fcdb9f403c3..8b5d2c7fcfe 100644 --- a/tests/testsSupport/src/main/call/models/VersionedInstanceType.kt +++ b/tests/testsSupport/src/main/call/models/VersionedInstanceType.kt @@ -17,9 +17,12 @@ */ package call.models +import kotlinx.serialization.Serializable + +@Serializable data class VersionedInstanceType( - val name: String, - val version: String + val name: String = "", + val version: String = "" ) { fun getPath(): String { From 4fff0f94e1bcc02ecf1fef1393e50636ad262463 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara <9083456+MohamadJaara@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:39:07 +0200 Subject: [PATCH 2/2] remove added defaults --- .../src/main/call/models/CallFlow.kt | 10 ++++----- .../src/main/call/models/Instance.kt | 22 +++++++++---------- .../src/main/call/models/Metrics.kt | 10 ++++----- .../main/call/models/VersionedInstanceType.kt | 4 ++-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/testsSupport/src/main/call/models/CallFlow.kt b/tests/testsSupport/src/main/call/models/CallFlow.kt index f149c1645cd..5de650d6dc4 100644 --- a/tests/testsSupport/src/main/call/models/CallFlow.kt +++ b/tests/testsSupport/src/main/call/models/CallFlow.kt @@ -23,19 +23,19 @@ import kotlinx.serialization.Serializable @Serializable data class CallFlow( @SerialName("audioPacketsReceived") - val audioPacketsReceived: Long = 0, + val audioPacketsReceived: Long, @SerialName("audioPacketsSent") - val audioPacketsSent: Long = 0, + val audioPacketsSent: Long, @SerialName("videoPacketsReceived") - val videoPacketsReceived: Long = 0, + val videoPacketsReceived: Long, @SerialName("videoPacketsSent") - val videoPacketsSent: Long = 0, + val videoPacketsSent: Long, @SerialName("remoteUserId") - val remoteUserId: String = "" + val remoteUserId: String ) { // Secondary constructor for parsing from raw pcStats string diff --git a/tests/testsSupport/src/main/call/models/Instance.kt b/tests/testsSupport/src/main/call/models/Instance.kt index 092fc2b5c00..27a808dfcb7 100644 --- a/tests/testsSupport/src/main/call/models/Instance.kt +++ b/tests/testsSupport/src/main/call/models/Instance.kt @@ -21,17 +21,17 @@ import kotlinx.serialization.Serializable @Serializable data class Instance( - val id: String? = null, - val instanceStatus: InstanceStatus? = null, - val currentCall: Call? = null, - val email: String? = null, - val password: String? = null, - val backend: String? = null, - val screenshot: String? = null, - val instanceType: VersionedInstanceType? = null, - val name: String? = null, - val timeout: Long? = null, - val created: Long? = null, + val id: String?, + val instanceStatus: InstanceStatus?, + val currentCall: Call?, + val email: String?, + val password: String?, + val backend: String?, + val screenshot: String?, + val instanceType: VersionedInstanceType?, + val name: String?, + val timeout: Long?, + val created: Long?, val message: String? = null ) { override fun toString(): String { diff --git a/tests/testsSupport/src/main/call/models/Metrics.kt b/tests/testsSupport/src/main/call/models/Metrics.kt index 4a0409f1897..e907dbef2fa 100644 --- a/tests/testsSupport/src/main/call/models/Metrics.kt +++ b/tests/testsSupport/src/main/call/models/Metrics.kt @@ -24,11 +24,11 @@ import kotlinx.serialization.Serializable @Serializable data class Metrics( - val success: Boolean = false, - val estabTimeMs: Long = 0, - val setupTimeMs: Long = 0, - val avgRateU: Long = 0, - val avgRateD: Long = 0 + val success: Boolean, + val estabTimeMs: Long, + val setupTimeMs: Long, + val avgRateU: Long, + val avgRateD: Long ) { override fun toString(): String { return "Metrics(success=$success, estab_time_ms=$estabTimeMs, " + diff --git a/tests/testsSupport/src/main/call/models/VersionedInstanceType.kt b/tests/testsSupport/src/main/call/models/VersionedInstanceType.kt index 8b5d2c7fcfe..1e23f59e9de 100644 --- a/tests/testsSupport/src/main/call/models/VersionedInstanceType.kt +++ b/tests/testsSupport/src/main/call/models/VersionedInstanceType.kt @@ -21,8 +21,8 @@ import kotlinx.serialization.Serializable @Serializable data class VersionedInstanceType( - val name: String = "", - val version: String = "" + val name: String, + val version: String ) { fun getPath(): String {