Android Kotlin Compose按字母解析JSON指定内容的问题求助
问题描述
开发一款词典应用,已将数据集转为JSON文件,希望实现用户点击对应字母时提取该字母相关搭配数据(如点击A展示A开头的搭配)。但在Android Kotlin Compose中用Gson解析JSON时遇到两个问题:
- 抛出
Expected BEGIN_ARRAY but was BEGIN_OBJECT错误 - 当前代码会加载全部数据,不符合按需提取需求
JSON文件示例
{ "A": [ { "collocation": "above average", "meaning": "more than average, esp. in amount, age, height, weight etc. " }, { "collocation": "absolutely necessary", "meaning": "totally or completely necessary" } ], "B": [ { "collocation": "back pay", "meaning": "money a worker earned in the past but hasn't been paid yet " }, { "collocation": "back road", "meaning": "a small country road " } ] }
View层代码
val jsonFileString = viewModel.getJsonDataFromCollocationsJson(context, fileName = "collocations.json") Log.i("data", jsonFileString ?: "null") val gson = Gson() val listCollocationType = object : TypeToken<List<Collocations>>() {}.type var collocations: List<Collocations> = gson.fromJson(jsonFileString, listCollocationType) collocations.forEachIndexed { idx, collocations -> Log.i("data", "> Item $idx:\n$collocations") }
ViewModel层代码
fun getJsonDataFromCollocationsJson(context: Context, fileName: String): String? { var jsonString: String? = null try { jsonString = context.assets.open(fileName).bufferedReader().use { it.readText() } } catch (e: IOException) { e.printStackTrace() return null } return jsonString }
错误日志
/AndroidRuntime: FATAL EXCEPTION: main Process: com.enestigli.dictionaryapp, PID: 5885 com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $ at com.google.gson.Gson.fromJson(Gson.java:939) at com.google.gson.Gson.fromJson(Gson.java:892) at com.google.gson.Gson.fromJson(Gson.java:841) at com.enestigli.dictionaryapp.presentation.CollocationScreen.CollocationScreenKt$gridView$2$1$1$1$1$1$1.invoke(CollocationScreen.kt:166) at com.enestigli.dictionaryapp.presentation.CollocationScreen.CollocationScreenKt$gridView$2$1$1$1$1$1$1.invoke(CollocationScreen.kt:158) at androidx.compose.foundation.ClickableKt$clickable$4$gesture$1$2.invoke-k-4lQ0M(Clickable.kt:153) at androidx.compose.foundation.ClickableKt$clickable$4$gesture$1$2.invoke(Clickable.kt:142) at androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1$1.invokeSuspend(TapGestureDetector.kt:220) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:178) at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166) at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397) at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431) at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420) at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328) at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter$PointerEventHandlerCoroutine.offerPointerEvent(SuspendingPointerInputFilter.kt:560) at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.dispatchPointerEvent(SuspendingPointerInputFilter.kt:452) at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.onPointerEvent-H0pRuoY(SuspendingPointerInputFilter.kt:465) at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:310) at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297) at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297) at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297) at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297) at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297) at androidx.compose.ui.input.pointer.NodeParent.dispatchMainEventPass(HitPathTracker.kt:179) at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges(HitPathTracker.kt:98) at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog(PointerInputEventProcessor.kt:80) at androidx.compose.ui.platform.AndroidComposeView.sendMotionEvent-8iAsVTc(AndroidComposeView.android.kt:1261) at androidx.compose.ui.platform.AndroidComposeView.handleMotionEvent-8iAsVTc(AndroidComposeView.android.kt:1211) at androidx.compose.ui.platform.AndroidComposeView.dispatchTouchEvent(AndroidComposeView.android.kt:1150) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3121) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2802) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3121) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2802) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3121) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2802) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3121) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2802) at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:498) at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1899) at android.app.Activity.dispatchTouchEvent(Activity.java:4262) E/AndroidRuntime: at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:456) at android.view.View.dispatchPointerEvent(View.java:15263) at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:6548) at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:6348) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5804) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5861) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5827) at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5992) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5835) at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:6049) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5808) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5861) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5827) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5835) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5808) at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:8857) at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:8808) at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:8777) at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:8980) at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:267) at android.os.MessageQueue.nativePollOnce(Native Method) at android.os.MessageQueue.next(MessageQueue.java:335) at android.os.Looper.loopOnce(Looper.java:161) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7898) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.MotionDurationScaleImpl@8b42480, androidx.compose.runtime.BroadcastFrameClock@2d2bab9, StandaloneCoroutine{Cancelling}@cb40efe, AndroidUiDispatcher@baff65f] Caused by: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $ at com.google.gson.stream.JsonReader.beginArray(JsonReader.java:350) at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:80) at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61) at com.google.gson.Gson.fromJson(Gson.java:927)
解决方案
1. 修复Gson解析错误
JSON外层是Object,键为字母、值为搭配数组,当前代码试图直接解析成List<Collocations>,导致类型不匹配错误。
步骤1:规范数据类定义
// 单个搭配的数据类 data class Collocation( val collocation: String, val meaning: String )
步骤2:修改解析逻辑
直接解析成Map<String, List<Collocation>>,匹配JSON结构:
val jsonFileString = viewModel.getJsonDataFromCollocationsJson(context, fileName = "collocations.json") jsonFileString?.let { json -> val gson = Gson() val collocationMapType = object : TypeToken<Map<String, List<Collocation>>>() {}.type val collocationMap: Map<String, List<Collocation>> = gson.fromJson(json, collocationMapType) // 点击字母A时获取对应数据 val aCollocations = collocationMap["A"] aCollocations?.forEachIndexed { idx, item -> Log.i("data", "> Item $idx: ${item.collocation} - ${item.meaning}") } }
2. 实现按需加载
方式1:流式解析(不加载全量数据)
用JsonReader流式读取,只解析目标字母对应的内容:
// ViewModel中新增方法 fun getCollocationsByLetter(context: Context, fileName: String, targetLetter: String): List<Collocation>? { return try { context.assets.open(fileName).use { inputStream -> Gson().newJsonReader(inputStream.bufferedReader()).use { reader -> reader.beginObject() var targetList: List<Collocation>? = null while (reader.hasNext()) { val key = reader.nextName() if (key == targetLetter) { val listType = object : TypeToken<List<Collocation>>() {}.type targetList = Gson().fromJson(reader, listType) reader.skipValue() // 跳过剩余内容提升性能 break } else { reader.skipValue() // 跳过非目标字母数据 } } reader.endObject() targetList } } } catch (e: IOException) { e.printStackTrace() null } }
View层调用:
// 点击字母时触发 viewModel.getCollocationsByLetter(context, "collocations.json", "A")?.let { collocations -> // 展示数据逻辑 }
方式2:拆分JSON文件(简单高效)
按字母拆分JSON文件(如collocations_a.json、collocations_b.json),点击时加载对应文件:
// ViewModel中修改加载方法 fun getCollocationsByLetter(context: Context, letter: String): List<Collocation>? { val fileName = "collocations_${letter.lowercase()}.json" return try { context.assets.open(fileName).bufferedReader().use { json -> val listType = object : TypeToken<List<Collocation>>() {}.type Gson().fromJson(json, listType) } } catch (e: IOException) { e.printStackTrace() null } }
内容的提问来源于stack exchange,提问作者NewPartizal
相关产品推荐
相关产品推荐

