From 476c65a19d482633eb53a3cf9820f5f15c71f26c Mon Sep 17 00:00:00 2001 From: David Schachter Date: Fri, 10 Jul 2026 13:54:47 -0700 Subject: [PATCH 1/6] ADFA-4634: Remove Jackson and kotlinx.serialization, consolidate on Gson Gson was already the project-wide JSON library (24 files, 11 modules). Jackson had a single use in WebServer.kt; replaced with Gson equivalent. kotlinx.serialization was entirely unused (dead import + declared deps only). Co-Authored-By: Claude Sonnet 4.6 --- app/build.gradle.kts | 5 ----- .../com/itsaky/androidide/localWebServer/WebServer.kt | 8 +++----- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f003dd493f..708b0c3c21 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -35,7 +35,6 @@ plugins { id("com.itsaky.androidide.desugaring") alias(libs.plugins.sentry) alias(libs.plugins.google.services) - kotlin("plugin.serialization") } fun propOrEnv(name: String): String = @@ -318,7 +317,6 @@ dependencies { implementation(libs.common.markwon.linkify) implementation(libs.commons.text.v1140) - implementation(libs.kotlinx.serialization.json) // Koin for Dependency Injection implementation(libs.koin.android) implementation(libs.androidx.security.crypto) @@ -340,9 +338,6 @@ dependencies { // Pebble template engine implementation("io.pebbletemplates:pebble:4.1.1") - - // Jackson JSON parsing dependency - implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2") } tasks.register("downloadDocDb") { diff --git a/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt b/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt index 4bdb6bc5c8..d75ecde6e3 100644 --- a/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt +++ b/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt @@ -25,10 +25,9 @@ import io.pebbletemplates.pebble.PebbleEngine import io.pebbletemplates.pebble.loader.StringLoader import java.util.concurrent.ConcurrentHashMap import io.pebbletemplates.pebble.template.PebbleTemplate -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import android.os.Environment.getExternalStorageDirectory -import kotlinx.serialization.builtins.UByteArraySerializer +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken import okio.ByteString.Companion.toByteString @@ -452,8 +451,7 @@ class WebServer(private val config: ServerConfig) { } // Load JSON data into a template context Map<> for instantiation - val mapper = ObjectMapper() - val context: Map = mapper.readValue(dbContent.toString(Charsets.UTF_8), object : TypeReference>() {}) + val context: Map = Gson().fromJson(dbContent.toString(Charsets.UTF_8), object : TypeToken>() {}.type) // Evaluate template with loaded data and return the output val sw = StringWriter() From 562bda0b82ebca4ca79e62e1db079ff62a7b7800 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Fri, 10 Jul 2026 13:56:24 -0700 Subject: [PATCH 2/6] ADFA-4634: Remove orphaned kotlinx.serialization entries from version catalog Co-Authored-By: Claude Sonnet 4.6 --- gradle/libs.versions.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8403c4e9cb..714f8f7941 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,7 +24,6 @@ koinAndroid = "4.1.1" kotlin = "2.3.0" kotlin-coroutines = "1.9.0" kotlinxCoroutinesCore = "1.10.2" -kotlinxSerializationJson = "1.9.0" lifecycleViewmodelKtx = "2.7.0" paletteKtx = "1.0.0" playServicesOssLicenses = "17.1.0" @@ -128,7 +127,6 @@ google-genai = { module = "com.google.genai:google-genai", version.ref = "google gson-v2101 = { module = "com.google.code.gson:gson", version.ref = "gson" } koin-android = { module = "io.insert-koin:koin-android", version.ref = "koinAndroid" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } -kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } @@ -307,7 +305,6 @@ tooling-slf4j = { module = "org.slf4j:slf4j-api", version = "2.0.12" } # Classpaths android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" } kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } -kotlin-serialization-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } nav-safe-args-gradle-plugin = { module = "androidx.navigation:navigation-safe-args-gradle-plugin", version.ref = "androidx-navigation" } maven-publish = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "maven-publish-plugin" } androidx-monitor = { group = "androidx.test", name = "monitor", version.ref = "monitor" } From d87814a1fff6f2f48b9f5ed2881d641fe0708525 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Fri, 10 Jul 2026 14:02:16 -0700 Subject: [PATCH 3/6] ADFA-4634: Remove kotlinx.serialization classpath from root build script Co-Authored-By: Claude Sonnet 4.6 --- build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 142e2c4ca4..c0d3b813ef 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -66,7 +66,6 @@ buildscript { dependencies { classpath(libs.kotlin.gradle.plugin) classpath(libs.nav.safe.args.gradle.plugin) - classpath(libs.kotlin.serialization.plugin) } } From 09203da68943a85f17901a4a82a651a59d23fae8 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Fri, 10 Jul 2026 14:07:07 -0700 Subject: [PATCH 4/6] ADFA-4634: Reuse Gson instance and TypeToken across requests Co-Authored-By: Claude Sonnet 4.6 --- .../java/com/itsaky/androidide/localWebServer/WebServer.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt b/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt index d75ecde6e3..5710d80898 100644 --- a/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt +++ b/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt @@ -70,6 +70,8 @@ class WebServer(private val config: ServerConfig) { private val brotliCompression : String = "br" private val pebbleEngine = PebbleEngine.Builder().loader(StringLoader()).build() private val templateCache = ConcurrentHashMap() + private val gson = Gson() + private val dbContextType = object : TypeToken>() {}.type private var bookshelfTemplateId : Int = -1; private val HTTP_INTERNAL_SERVER_ERROR = 500 private val HTTP_NOT_FOUND = 404 @@ -451,7 +453,7 @@ class WebServer(private val config: ServerConfig) { } // Load JSON data into a template context Map<> for instantiation - val context: Map = Gson().fromJson(dbContent.toString(Charsets.UTF_8), object : TypeToken>() {}.type) + val context: Map = gson.fromJson(dbContent.toString(Charsets.UTF_8), dbContextType) // Evaluate template with loaded data and return the output val sw = StringWriter() From a8237c325e59f04bcee30d30f0d2520aa2e09d5b Mon Sep 17 00:00:00 2001 From: David Schachter Date: Fri, 10 Jul 2026 14:57:40 -0700 Subject: [PATCH 5/6] ADFA-4634: Normalize Gson numbers to Long and guard empty/null JSON context - Register a TypeAdapterFactory that returns Long (not Double) for whole numbers when deserializing Map, matching Jackson's behavior - Throw explicitly before fromJson() for blank or "null" JSON strings so the caller gets a meaningful error instead of a null map Co-Authored-By: Claude Sonnet 4.6 --- .../androidide/localWebServer/WebServer.kt | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt b/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt index 5710d80898..55e0bcf0fb 100644 --- a/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt +++ b/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt @@ -27,7 +27,13 @@ import java.util.concurrent.ConcurrentHashMap import io.pebbletemplates.pebble.template.PebbleTemplate import android.os.Environment.getExternalStorageDirectory import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken +import com.google.gson.stream.JsonWriter import okio.ByteString.Companion.toByteString @@ -70,7 +76,23 @@ class WebServer(private val config: ServerConfig) { private val brotliCompression : String = "br" private val pebbleEngine = PebbleEngine.Builder().loader(StringLoader()).build() private val templateCache = ConcurrentHashMap() - private val gson = Gson() + private val gson: Gson = GsonBuilder() + .registerTypeAdapterFactory(object : TypeAdapterFactory { + @Suppress("UNCHECKED_CAST") + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + if (type.rawType != Any::class.java) return null + val delegate = gson.getDelegateAdapter(this, type) + return object : TypeAdapter() { + override fun write(out: JsonWriter, value: T?) = delegate.write(out, value) + override fun read(inp: JsonReader): T? { + if (inp.peek() != JsonToken.NUMBER) return delegate.read(inp) + val s = inp.nextString() + return (if ('.' in s) s.toDouble() else s.toLong()) as T + } + } + } + }) + .create() private val dbContextType = object : TypeToken>() {}.type private var bookshelfTemplateId : Int = -1; private val HTTP_INTERNAL_SERVER_ERROR = 500 @@ -453,7 +475,10 @@ class WebServer(private val config: ServerConfig) { } // Load JSON data into a template context Map<> for instantiation - val context: Map = gson.fromJson(dbContent.toString(Charsets.UTF_8), dbContextType) + val dbContentStr = dbContent.toString(Charsets.UTF_8) + if (dbContentStr.isBlank() || dbContentStr.trim() == "null") + throw Exception("Template ID $templateId has empty or null JSON context") + val context: Map = gson.fromJson(dbContentStr, dbContextType) // Evaluate template with loaded data and return the output val sw = StringWriter() From afb3f80de33f8626eaef1d50381ce3f68d3d12ff Mon Sep 17 00:00:00 2001 From: David Schachter Date: Fri, 10 Jul 2026 15:52:36 -0700 Subject: [PATCH 6/6] ADFA-4634: Use ToNumberPolicy.LONG_OR_DOUBLE for correct integer deserialization Co-Authored-By: Claude Sonnet 4.6 --- .../androidide/localWebServer/WebServer.kt | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt b/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt index 55e0bcf0fb..7ef3ac76e3 100644 --- a/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt +++ b/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt @@ -28,12 +28,8 @@ import io.pebbletemplates.pebble.template.PebbleTemplate import android.os.Environment.getExternalStorageDirectory import com.google.gson.Gson import com.google.gson.GsonBuilder -import com.google.gson.TypeAdapter -import com.google.gson.TypeAdapterFactory +import com.google.gson.ToNumberPolicy import com.google.gson.reflect.TypeToken -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonToken -import com.google.gson.stream.JsonWriter import okio.ByteString.Companion.toByteString @@ -76,22 +72,8 @@ class WebServer(private val config: ServerConfig) { private val brotliCompression : String = "br" private val pebbleEngine = PebbleEngine.Builder().loader(StringLoader()).build() private val templateCache = ConcurrentHashMap() - private val gson: Gson = GsonBuilder() - .registerTypeAdapterFactory(object : TypeAdapterFactory { - @Suppress("UNCHECKED_CAST") - override fun create(gson: Gson, type: TypeToken): TypeAdapter? { - if (type.rawType != Any::class.java) return null - val delegate = gson.getDelegateAdapter(this, type) - return object : TypeAdapter() { - override fun write(out: JsonWriter, value: T?) = delegate.write(out, value) - override fun read(inp: JsonReader): T? { - if (inp.peek() != JsonToken.NUMBER) return delegate.read(inp) - val s = inp.nextString() - return (if ('.' in s) s.toDouble() else s.toLong()) as T - } - } - } - }) + private val gson: Gson = GsonBuilder() + .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .create() private val dbContextType = object : TypeToken>() {}.type private var bookshelfTemplateId : Int = -1;