Skip to content

Commit a8237c3

Browse files
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<String, Any>, 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 <noreply@anthropic.com>
1 parent 09203da commit a8237c3

1 file changed

Lines changed: 27 additions & 2 deletions

File tree

  • app/src/main/java/com/itsaky/androidide/localWebServer

‎app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt‎

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ import java.util.concurrent.ConcurrentHashMap
2727
import io.pebbletemplates.pebble.template.PebbleTemplate
2828
import android.os.Environment.getExternalStorageDirectory
2929
import com.google.gson.Gson
30+
import com.google.gson.GsonBuilder
31+
import com.google.gson.TypeAdapter
32+
import com.google.gson.TypeAdapterFactory
3033
import com.google.gson.reflect.TypeToken
34+
import com.google.gson.stream.JsonReader
35+
import com.google.gson.stream.JsonToken
36+
import com.google.gson.stream.JsonWriter
3137
import okio.ByteString.Companion.toByteString
3238

3339

@@ -70,7 +76,23 @@ class WebServer(private val config: ServerConfig) {
7076
private val brotliCompression : String = "br"
7177
private val pebbleEngine = PebbleEngine.Builder().loader(StringLoader()).build()
7278
private val templateCache = ConcurrentHashMap<Int, PebbleTemplate>()
73-
private val gson = Gson()
79+
private val gson: Gson = GsonBuilder()
80+
.registerTypeAdapterFactory(object : TypeAdapterFactory {
81+
@Suppress("UNCHECKED_CAST")
82+
override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
83+
if (type.rawType != Any::class.java) return null
84+
val delegate = gson.getDelegateAdapter(this, type)
85+
return object : TypeAdapter<T>() {
86+
override fun write(out: JsonWriter, value: T?) = delegate.write(out, value)
87+
override fun read(inp: JsonReader): T? {
88+
if (inp.peek() != JsonToken.NUMBER) return delegate.read(inp)
89+
val s = inp.nextString()
90+
return (if ('.' in s) s.toDouble() else s.toLong()) as T
91+
}
92+
}
93+
}
94+
})
95+
.create()
7496
private val dbContextType = object : TypeToken<Map<String, Any>>() {}.type
7597
private var bookshelfTemplateId : Int = -1;
7698
private val HTTP_INTERNAL_SERVER_ERROR = 500
@@ -453,7 +475,10 @@ class WebServer(private val config: ServerConfig) {
453475
}
454476

455477
// Load JSON data into a template context Map<> for instantiation
456-
val context: Map<String, Any> = gson.fromJson(dbContent.toString(Charsets.UTF_8), dbContextType)
478+
val dbContentStr = dbContent.toString(Charsets.UTF_8)
479+
if (dbContentStr.isBlank() || dbContentStr.trim() == "null")
480+
throw Exception("Template ID $templateId has empty or null JSON context")
481+
val context: Map<String, Any> = gson.fromJson(dbContentStr, dbContextType)
457482

458483
// Evaluate template with loaded data and return the output
459484
val sw = StringWriter()

0 commit comments

Comments
 (0)