Kotlin中嵌套HashMap<String, HashMap<String, HashMap<String,String>>>解析与图表数据填充问题咨询
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

