compose-webview-multiplatform icon indicating copy to clipboard operation
compose-webview-multiplatform copied to clipboard

MissingFieldException: 'body' field missing in GitHubRelease deserialization

Open dsqrwym opened this issue 6 months ago • 2 comments

Description

When initializing KCEF with GitHub releases download, I encountered a serialization error: 'body' is required for type with serial name 'dev.datlag.kcef.model.GitHubRelease', but it was missing at path: $

io.ktor.serialization.JsonConvertException: Illegal input: Field 'body' is required for type with serial name 'dev.datlag.kcef.model.GitHubRelease', but it was missing at path: $
	at io.ktor.serialization.kotlinx.KotlinxSerializationConverter.deserialize(KotlinxSerializationConverter.kt:77)
	at io.ktor.serialization.ContentConverterKt$deserialize$$inlined$map$1$2.emit(Emitters.kt:51)
	at kotlinx.coroutines.flow.FlowKt__BuildersKt$asFlow$$inlined$unsafeFlow$3.collect(SafeCollector.common.kt:111)
	at io.ktor.serialization.ContentConverterKt$deserialize$$inlined$map$1.collect(SafeCollector.common.kt:109)
	at kotlinx.coroutines.flow.FlowKt__ReduceKt.firstOrNull(Reduce.kt:247)
	at kotlinx.coroutines.flow.FlowKt.firstOrNull(Unknown Source)
	at io.ktor.serialization.ContentConverterKt.deserialize(ContentConverter.kt:97)
	at io.ktor.client.plugins.contentnegotiation.ContentNegotiationKt.ContentNegotiation$lambda$13$convertResponse(ContentNegotiation.kt:234)
	at io.ktor.client.plugins.contentnegotiation.ContentNegotiationKt.access$ContentNegotiation$lambda$13$convertResponse(ContentNegotiation.kt:1)
	at io.ktor.client.plugins.contentnegotiation.ContentNegotiationKt$ContentNegotiation$2$2.invokeSuspend(ContentNegotiation.kt:249)
	at io.ktor.client.plugins.contentnegotiation.ContentNegotiationKt$ContentNegotiation$2$2.invoke(ContentNegotiation.kt)
	at io.ktor.client.plugins.contentnegotiation.ContentNegotiationKt$ContentNegotiation$2$2.invoke(ContentNegotiation.kt)
	at io.ktor.client.plugins.api.TransformResponseBodyHook$install$1.invokeSuspend(KtorCallContexts.kt:104)
	at io.ktor.client.plugins.api.TransformResponseBodyHook$install$1.invoke(KtorCallContexts.kt)
	at io.ktor.client.plugins.api.TransformResponseBodyHook$install$1.invoke(KtorCallContexts.kt)
	at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:79)
	at io.ktor.util.pipeline.DebugPipelineContext.proceed(DebugPipelineContext.kt:57)
	at io.ktor.client.HttpClient$4.invokeSuspend(HttpClient.kt:179)
	at io.ktor.client.HttpClient$4.invoke(HttpClient.kt)
	at io.ktor.client.HttpClient$4.invoke(HttpClient.kt)
	at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:79)
	at io.ktor.util.pipeline.DebugPipelineContext.proceed(DebugPipelineContext.kt:57)
	at io.ktor.client.plugins.ReceiveError$install$1.invokeSuspend(HttpCallValidator.kt:149)
	at io.ktor.client.plugins.ReceiveError$install$1.invoke(HttpCallValidator.kt)
	at io.ktor.client.plugins.ReceiveError$install$1.invoke(HttpCallValidator.kt)
	at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:79)
	at io.ktor.util.pipeline.DebugPipelineContext.proceed(DebugPipelineContext.kt:57)
	at io.ktor.util.pipeline.DebugPipelineContext.execute$ktor_utils(DebugPipelineContext.kt:63)
	at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:86)
	at io.ktor.client.call.HttpClientCall.bodyNullable(HttpClientCall.kt:87)
	at dev.datlag.kcef.KCEFBuilder$Download$Transform$Companion$GitHub$1.transform(KCEFBuilder.kt:881)
	at dev.datlag.kcef.step.fetch.PackageDownloader.downloadPackage(PackageDownloader.kt:30)
	at dev.datlag.kcef.step.fetch.PackageDownloader$downloadPackage$1.invokeSuspend(PackageDownloader.kt)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
Caused by: kotlinx.serialization.MissingFieldException: Field 'body' is required for type with serial name 'dev.datlag.kcef.model.GitHubRelease', but it was missing at path: $
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:95)
	at kotlinx.serialization.json.Json.decodeFromString(Json.kt:149)
Caused by: kotlinx.serialization.MissingFieldException: Field 'body' is required for type with serial name 'dev.datlag.kcef.model.GitHubRelease', but it was missing at path: $

	at io.ktor.serialization.kotlinx.KotlinxSerializationConverter.deserialize(KotlinxSerializationConverter.kt:69)
	... 40 more
Caused by: kotlinx.serialization.MissingFieldException: Field 'body' is required for type with serial name 'dev.datlag.kcef.model.GitHubRelease', but it was missing
	at kotlinx.serialization.internal.PluginExceptionsKt.throwMissingFieldException(PluginExceptions.kt:20)
Caused by: kotlinx.serialization.MissingFieldException: Field 'body' is required for type with serial name 'dev.datlag.kcef.model.GitHubRelease', but it was missing

	at dev.datlag.kcef.model.GitHubRelease.<init>(GitHubRelease.kt:6)
	at dev.datlag.kcef.model.GitHubRelease$$serializer.deserialize(GitHubRelease.kt:6)
	at dev.datlag.kcef.model.GitHubRelease$$serializer.deserialize(GitHubRelease.kt:6)
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:69)
	... 42 more
io.ktor.serialization.JsonConvertException: Illegal input: Field 'body' is required for type with serial name 'dev.datlag.kcef.model.GitHubRelease', but it was missing at path: $
	at io.ktor.serialization.kotlinx.KotlinxSerializationConverter.deserialize(KotlinxSerializationConverter.kt:77)
	at io.ktor.serialization.ContentConverterKt$deserialize$$inlined$map$1$2.emit(Emitters.kt:51)
	at kotlinx.coroutines.flow.FlowKt__BuildersKt$asFlow$$inlined$unsafeFlow$3.collect(SafeCollector.common.kt:111)
	at io.ktor.serialization.ContentConverterKt$deserialize$$inlined$map$1.collect(SafeCollector.common.kt:109)
	at kotlinx.coroutines.flow.FlowKt__ReduceKt.firstOrNull(Reduce.kt:247)
	at kotlinx.coroutines.flow.FlowKt.firstOrNull(Unknown Source)
	at io.ktor.serialization.ContentConverterKt.deserialize(ContentConverter.kt:97)
	at io.ktor.client.plugins.contentnegotiation.ContentNegotiationKt.ContentNegotiation$lambda$13$convertResponse(ContentNegotiation.kt:234)
	at io.ktor.client.plugins.contentnegotiation.ContentNegotiationKt.access$ContentNegotiation$lambda$13$convertResponse(ContentNegotiation.kt:1)
	at io.ktor.client.plugins.contentnegotiation.ContentNegotiationKt$ContentNegotiation$2$2.invokeSuspend(ContentNegotiation.kt:249)
	at io.ktor.client.plugins.contentnegotiation.ContentNegotiationKt$ContentNegotiation$2$2.invoke(ContentNegotiation.kt)
	at io.ktor.client.plugins.contentnegotiation.ContentNegotiationKt$ContentNegotiation$2$2.invoke(ContentNegotiation.kt)
	at io.ktor.client.plugins.api.TransformResponseBodyHook$install$1.invokeSuspend(KtorCallContexts.kt:104)
	at io.ktor.client.plugins.api.TransformResponseBodyHook$install$1.invoke(KtorCallContexts.kt)
	at io.ktor.client.plugins.api.TransformResponseBodyHook$install$1.invoke(KtorCallContexts.kt)
	at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:79)
	at io.ktor.util.pipeline.DebugPipelineContext.proceed(DebugPipelineContext.kt:57)
	at io.ktor.client.HttpClient$4.invokeSuspend(HttpClient.kt:179)
	at io.ktor.client.HttpClient$4.invoke(HttpClient.kt)
	at io.ktor.client.HttpClient$4.invoke(HttpClient.kt)
	at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:79)
	at io.ktor.util.pipeline.DebugPipelineContext.proceed(DebugPipelineContext.kt:57)
	at io.ktor.client.plugins.ReceiveError$install$1.invokeSuspend(HttpCallValidator.kt:149)
	at io.ktor.client.plugins.ReceiveError$install$1.invoke(HttpCallValidator.kt)
	at io.ktor.client.plugins.ReceiveError$install$1.invoke(HttpCallValidator.kt)
	at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:79)
	at io.ktor.util.pipeline.DebugPipelineContext.proceed(DebugPipelineContext.kt:57)
	at io.ktor.util.pipeline.DebugPipelineContext.execute$ktor_utils(DebugPipelineContext.kt:63)
	at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:86)
	at io.ktor.client.call.HttpClientCall.bodyNullable(HttpClientCall.kt:87)
	at dev.datlag.kcef.KCEFBuilder$Download$Transform$Companion$GitHub$1.transform(KCEFBuilder.kt:881)
	at dev.datlag.kcef.step.fetch.PackageDownloader.downloadPackage(PackageDownloader.kt:30)
	at dev.datlag.kcef.step.fetch.PackageDownloader$downloadPackage$1.invokeSuspend(PackageDownloader.kt)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
Caused by: kotlinx.serialization.MissingFieldException: Field 'body' is required for type with serial name 'dev.datlag.kcef.model.GitHubRelease', but it was missing at path: $
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:95)
Caused by: kotlinx.serialization.MissingFieldException: Field 'body' is required for type with serial name 'dev.datlag.kcef.model.GitHubRelease', but it was missing at path: $

	at kotlinx.serialization.json.Json.decodeFromString(Json.kt:149)
	at io.ktor.serialization.kotlinx.KotlinxSerializationConverter.deserialize(KotlinxSerializationConverter.kt:69)
	... 40 more
Caused by: kotlinx.serialization.MissingFieldException: Field 'body' is required for type with serial name 'dev.datlag.kcef.model.GitHubRelease', but it was missing
Caused by: kotlinx.serialization.MissingFieldException: Field 'body' is required for type with serial name 'dev.datlag.kcef.model.GitHubRelease', but it was missing

	at kotlinx.serialization.internal.PluginExceptionsKt.throwMissingFieldException(PluginExceptions.kt:20)
	at dev.datlag.kcef.model.GitHubRelease.<init>(GitHubRelease.kt:6)
	at dev.datlag.kcef.model.GitHubRelease$$serializer.deserialize(GitHubRelease.kt:6)
	at dev.datlag.kcef.model.GitHubRelease$$serializer.deserialize(GitHubRelease.kt:6)
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:69)
	... 42 more

Steps to reproduce

Use the following code in your desktop main() function:

package org.dsqrwym.standard

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.ProgressIndicatorDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import com.multiplatform.webview.util.addTempDirectoryRemovalHook
import dev.datlag.kcef.KCEF
import dev.datlag.kcef.KCEFBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.dsqrwym.shared.AppRoot
import java.io.File
import kotlin.math.max

fun main() = application {
   addTempDirectoryRemovalHook()
   Window(
       onCloseRequest = ::exitApplication,
       title = "Project",
   ) {
       var restartRequired by remember { mutableStateOf(false) }
       var downloading by remember { mutableStateOf(0F) }
       var initialized by remember { mutableStateOf(false) }
       val download: KCEFBuilder.Download = remember { KCEFBuilder.Download.Builder().github().build() }

       LaunchedEffect(Unit) {
           withContext(Dispatchers.IO) {
               KCEF.init(builder = {
                   installDir(File("kcef-bundle"))

                   /*
                     Add this code when using JDK 17.
                     Builder().github {
                         release("jbr-release-17.0.10b1087.23")
                     }.buffer(download.bufferSize).build()
                    */
                   progress {
                       onDownloading {
                           downloading = max(it, 0F)
                       }
                       onInitialized {
                           initialized = true
                       }
                   }
                   settings {
                       cachePath = File("cache").absolutePath
                   }
               }, onError = {
                   it?.printStackTrace()
               }, onRestartRequired = {
                   restartRequired = true
               })
           }
       }

       if (!restartRequired) {
           if (initialized) {
               App()
           } else {
               AppRoot {
                   Column(
                       modifier = Modifier.fillMaxSize(),
                       horizontalAlignment = Alignment.CenterHorizontally,
                       verticalArrangement = Arrangement.Center
                   ) {
                       LinearProgressIndicator(
                           progress = { downloading },
                           modifier = Modifier,
                           color = ProgressIndicatorDefaults.linearColor,
                           trackColor = ProgressIndicatorDefaults.linearTrackColor,
                           strokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
                       )
                       Text(text = "Downloading $downloading%")
                   }
               }
           }
       }

       DisposableEffect(Unit) {
           onDispose {
               KCEF.disposeBlocking()
           }
       }
   }
}
  1. Use KCEFBuilder.Download.Builder().github().build() as shown.
  2. Run the application and wait for initialization.
  3. The error occurs during deserialization of GitHub release data.

Expected Behavior

The body field in GitHubRelease should be optional (String? = null) to support releases that do not have a body. This would prevent deserialization failures.

Environment

  • compose-webview-multiplatform version: 2.0.0
  • JDK version: 11
  • OS: Windows 11
  • Kotlin version: 2.1.21

Thanks for your work on this project, make OPEN SOURCE great

dsqrwym avatar Jun 13 '25 23:06 dsqrwym

I got the same issue, but I solved it.

You can see that the request URL is "https://api.github.com/repos/JetBrains/JetBrainsRuntime/releases/tags/jbr-release-17.0.12b1207.37." if you set breakpoints. When u go to browse the URL directly, you will receive the following response:

{ "message": "API rate limit exceeded for 43.132.141.13. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)", "documentation_url": "https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting" }

This is because GitHub limits requests when it is not in a logged-in status. See[Rate limits for the REST API](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28)) , So, just need to solve this problem.

Following code works for me:

val customClient = HttpClient {
    followRedirects = true
    install(ContentNegotiation) {
        json(
            Json {
                ignoreUnknownKeys = true
            })
    }
    install(HttpTimeout) {
        // If the network speed is slow, you can increase this value appropriately.
        requestTimeoutMillis = 60 * 1000
    }
    install(Auth) {
        bearer {
            loadTokens {
                // token from https://github.com/settings/tokens
                BearerTokens("ghp_xxxxxxxxxxxxxxxxxx", "ghp_xxxxxxxxxxxxxxxxxx")
            }
        }
    }
}

KCEF.init(builder = {
     download {
         github {
             release("jbr-release-17.0.12b1207.37")
           }
            client(client = customClient)
        }
        ... other config
    }

In addition, install(Auth) need dependency: implementation("io.ktor:ktor-client-auth:$ktor_version")

VomPom avatar Jul 03 '25 09:07 VomPom

All right 👍🏻, thanks 🙏🏻 so much

dsqrwym avatar Aug 12 '25 11:08 dsqrwym