You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中嵌套HashMap<String, HashMap<String, HashMap<String,String>>>解析与图表数据填充问题咨询

JSON解析与图表数据填充:graphData取值报错修复及优化方案

Hey there! Let's break down your problem step by step—first fixing the immediate error, then talking about whether your current approach is optimal and better alternatives.

Why the Error Happens

First, let's look at your data class definition for OtherData:

data class OtherData(
    var countiesData : Any,
    var transactionTypesData: Any,
    var graphData : HashMap<String,HashMap<String,HashMap<String,String>>>
)

Your graphData is a 3-level HashMap: 年份 -> 状态类型(比如SUCCESS)-> 月份 -> 数值.

When you run graphYear.getValue(graphYear.keys.elementAt(0)), you're getting the second level HashMap (the one mapped to the year, e.g., the SUCCESS container), not a string. Assigning this HashMap directly to tvFirstGraphLabel.text (which expects a String) causes a type mismatch error—this is why your marked line fails.

Also, using elementAt(0) on keys is unsafe: if graphYear is empty, this will throw an IndexOutOfBoundsException.

Fixing the Error with Your Current Approach

If you want to stick with your existing HashMap setup, here's how to safely retrieve the values you need and fix the error:

it.data.data.otherData.graphData.let { graphYearMap ->
    // Safely get the first year (avoid elementAt(0) to prevent crashes)
    val firstYear = graphYearMap.keys.firstOrNull() ?: return@let
    tvYearForRevenueReport.text = firstYear

    // Get the status map (e.g., SUCCESS) for the first year
    val statusMap = graphYearMap[firstYear] ?: return@let
    // Safely get the first status type (e.g., SUCCESS)
    val firstStatus = statusMap.keys.firstOrNull() ?: return@let
    tvFirstGraphLabel.text = firstStatus

    // If you need to access month data (e.g., count for month 9)
    val monthData = statusMap[firstStatus] ?: return@let
    val month9Count = monthData["9"] // This gives you "1" as a string
    // Convert to Int if needed for your chart: month9Count?.toInt()
}

This code uses firstOrNull() to handle empty cases gracefully, and explicitly navigates each level of the HashMap to get the correct string value for your TextView.

Is Your Current Approach Optimal?

No—using Any for countiesData/transactionTypesData and nested HashMaps has several drawbacks:

  • Type unsafety: You have no compile-time checks for the structure of your data, making it easy to access the wrong level or type.
  • Poor readability: Nested HashMaps make the code hard to follow—other developers (or future you) will struggle to understand what each level represents.
  • Error-prone: Manual key access increases the chance of typos or runtime crashes (e.g., accessing a non-existent key).

Better Alternative: Use Strongly-Typed Data Classes

The best approach is to define specific data classes that mirror your JSON structure. This gives you compile-time safety, better readability, and easier data access.

Step 1: Define Nested Data Classes

// Represents monthly counts for a status type (e.g., SUCCESS)
data class MonthlyTransactionData(
    val `1`: Int,
    val `2`: Int,
    val `3`: Int,
    val `4`: Int,
    val `5`: Int,
    val `6`: Int,
    val `7`: Int,
    val `8`: Int,
    val `9`: Int,
    val `10`: Int,
    val `11`: Int,
    val `12`: Int
)

// Represents status types (e.g., SUCCESS) for a year
data class YearlyStatusData(
    val SUCCESS: MonthlyTransactionData
    // Add other status types here if your JSON includes them (e.g., PENDING)
)

// Represents all data under otherData
data class OtherData(
    val countiesData: Map<String, Map<String, Int>>, // Year -> Country -> Count
    val transactionTypesData: Map<String, Map<String, Int>>, // Year -> Type -> Count
    val graphData: Map<String, YearlyStatusData>, // Year -> Status Data
    val currentMoth: Int,
    val preMonthAvr: Int
)

We use Map instead of HashMap for immutability (preferred in Kotlin), and specify exact types for all fields instead of Any.

Step 2: Access Data Safely

With these data classes, accessing your graph data becomes straightforward and safe:

it.data.data.otherData.graphData.let { graphYearMap ->
    val firstYearEntry = graphYearMap.entries.firstOrNull() ?: return@let
    val year = firstYearEntry.key
    val statusData = firstYearEntry.value

    tvYearForRevenueReport.text = year
    tvFirstGraphLabel.text = "SUCCESS" // Or dynamically get status keys if needed

    // Access monthly data for chart filling
    val monthlyCounts = listOf(
        statusData.SUCCESS.`1`,
        statusData.SUCCESS.`2`,
        statusData.SUCCESS.`3`,
        statusData.SUCCESS.`4`,
        statusData.SUCCESS.`5`,
        statusData.SUCCESS.`6`,
        statusData.SUCCESS.`7`,
        statusData.SUCCESS.`8`,
        statusData.SUCCESS.`9`,
        statusData.SUCCESS.`10`,
        statusData.SUCCESS.`11`,
        statusData.SUCCESS.`12`
    )
    // Use monthlyCounts to populate your chart
}

If your status types are dynamic (not just SUCCESS), you can iterate over statusData.entries to get all available statuses.

Final Notes

Using strongly-typed data classes is the industry standard for JSON parsing in Kotlin (especially with libraries like Moshi or Gson, which can auto-generate these classes for you). It eliminates runtime errors, makes your code easier to maintain, and aligns with Kotlin's focus on type safety.

内容的提问来源于stack exchange,提问作者Manzoor Ahmad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 12:32:38