使用Ktor与Kotlinx Serialization反序列化JSON数组时出错
问题描述
我尝试使用Ktor和Kotlinx Serialization获取模拟帖子数据并反序列化JSON数组,但遇到以下错误:
Error:Expected class kotlinx.serialization.json.JsonObject (Kotlin reflection is not available) as the serialized body of kotlinx.serialization.Polymorphic<List>, but had class kotlinx.serialization.json.JsonArray (Kotlin reflection is not available)
我找不到代码里哪里指定了期望数据为JsonObject而非JsonArray,这应该是错误原因,求帮忙定位。
相关代码
LoginRegisterFragment.kt
package com.example.groupupandroid import android.content.Intent import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.lifecycleScope import androidx.navigation.Navigation import androidx.navigation.fragment.findNavController import com.example.groupupandroid.databinding.ActivityMainBinding import com.example.groupupandroid.databinding.FragmentLoginRegisterBinding import data.remote.PostResponse import data.remote.PostsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch class LoginRegisterFragment : Fragment(){ // Getting xml objects private var binding: FragmentLoginRegisterBinding? = null // Creating service for networking private lateinit var service: PostsService private var posts: List<PostResponse> = emptyList() //@Composable override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { binding = FragmentLoginRegisterBinding.inflate(layoutInflater) lifecycleScope.launch {getPosts()} // Inflate the layout for this fragment return binding?.root } private suspend fun getPosts() { service = PostsService.create() posts = service.getPosts() print(posts) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // If register button is tapped make register visible binding?.registerToggleButton?.setOnClickListener { binding?.registerFields?.visibility = View.VISIBLE binding?.loginFields?.visibility = View.GONE } // If login button is tapped make login visible binding?.loginToggleButton?.setOnClickListener { binding?.registerFields?.visibility = View.GONE binding?.loginFields?.visibility = View.VISIBLE } // If login button is pushed swap to maps binding?.loginButton?.setOnClickListener { findNavController().navigate(R.id.loginToHomeScreen) } // If register button is pushed swap to maps binding?.registerButton?.setOnClickListener { findNavController().navigate(R.id.loginToHomeScreen) } binding?.materialButtonToggleGroup?.check(binding?.loginToggleButton!!.id) } override fun onDestroyView() { super.onDestroyView() binding = null } }
PostResponse.kt
package data.remote import kotlinx.serialization.Serializable @Serializable data class PostResponse ( val body: String, val title: String, val id: Int, val userId: Int )
PostsServiceImplementation.kt
package data.remote import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.util.* import java.lang.Exception class PostsServiceImplementation(private val client: HttpClient): PostsService { override suspend fun getPosts(): List<PostResponse> { return try { client.get {url(HttpRoutes.POSTS)}.body() } catch(e: Exception){ println("Error:${e.message}") emptyList() } } override suspend fun createPosts(postRequest: PostRequest): PostResponse? { return try { client.post { url(HttpRoutes.POSTS) contentType(ContentType.Application.Json) setBody(postRequest) }.body() } catch(e: Exception) { println("Error:${e.message}") null } } }
HttpRoutes.kt
package data.remote object HttpRoutes { private const val BASE_URL = "https://jsonplaceholder.typicode.com" const val POSTS = "$BASE_URL/posts" }
PostsService.kt
package data.remote import io.ktor.client.* import io.ktor.client.engine.android.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.kotlinx.serializer.* import io.ktor.client.plugins.logging.* import io.ktor.client.statement.* import io.ktor.serialization.kotlinx.json.* interface PostsService { suspend fun getPosts(): List<PostResponse> suspend fun createPosts(postRequest: PostRequest): PostResponse? companion object { fun create():PostsService { return PostsServiceImplementation ( client = HttpClient(Android) { install(Logging) { level = LogLevel.ALL } install(ContentNegotiation) { json() } } ) } } }
PostRequest.kt
package data.remote import kotlinx.serialization.Serializable @Serializable data class PostRequest ( val body: String, val title: String, val userId: Int )
问题原因与解决方法
这个错误的核心是:Ktor在反序列化泛型List类型时,默认触发了多态序列化逻辑,误以为要解析成JsonObject,但接口实际返回的是纯JSON数组。你并没有显式指定期望JsonObject,但泛型类型的序列化需要明确的序列化器支持。
解决方法:显式指定数组序列化器
修改PostsServiceImplementation中的getPosts方法,调用body时传入ListSerializer,明确告诉序列化器要解析的是PostResponse的数组:
import kotlinx.serialization.builtins.ListSerializer override suspend fun getPosts(): List<PostResponse> { return try { client.get(HttpRoutes.POSTS).body(ListSerializer(PostResponse.serializer())) } catch(e: Exception){ println("Error:${e.message}") emptyList() } }
这样就能正确识别接口返回的JSON数组,完成反序列化。
内容的提问来源于stack exchange,提问作者Kafka
相关产品推荐
相关产品推荐

