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

如何用Retrofit解析API进度数据并构建Model?数据类型与检索方案

How to Model and Access Dynamic Key JSON with Retrofit

First, let's break down your JSON structure: you have a top-level progress object that maps course IDs to course data. Each course has fixed fields (name, type, progress) plus dynamic module IDs mapping to module data (which also has fixed fields).

To handle these dynamic keys while keeping type safety, we'll use maps for the dynamic parts and Gson's @JsonAnyGetter/@JsonAnySetter annotations to mix fixed and dynamic fields in our model classes.


Step 1: Define the Model Classes

1.1 Module Class (Fixed Fields)

This represents each module with its static properties:

data class Module(
    val name: String,
    val type: String,
    val progress: Int
)

1.2 Course Class (Fixed + Dynamic Fields)

Each course has fixed fields plus dynamic module entries. We use @JsonAnySetter to capture unknown fields (the module IDs) into a map, and @JsonAnyGetter to expose them. We also add an operator method to enable bracket notation access:

import com.google.gson.annotations.JsonAnyGetter
import com.google.gson.annotations.JsonAnySetter
import com.google.gson.annotations.JsonIgnore

data class Course(
    val name: String,
    val type: String,
    val progress: Double,
    @JsonIgnore private val modules: MutableMap<String, Module> = mutableMapOf()
) {
    // Capture any dynamic module fields into the modules map
    @JsonAnySetter
    fun setModule(moduleId: String, module: Module) {
        modules[moduleId] = module
    }

    // Expose the modules map for serialization (if needed)
    @JsonAnyGetter
    fun getModules(): Map<String, Module> = modules

    // Enable bracket notation access (e.g., course["moduleId"])
    operator fun get(moduleId: String): Module? = modules[moduleId]
}

1.3 Top-Level Response Class

This wraps the entire response, mapping course IDs to Course objects:

import com.google.gson.annotations.SerializedName

data class ProgressResponse(
    @SerializedName("progress") val progress: Map<String, Course>
)

Step 2: Set Up Retrofit

Make sure you have the Gson converter added to your Retrofit instance (this is standard for JSON parsing):

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

val retrofit = Retrofit.Builder()
    .baseUrl("YOUR_API_BASE_URL")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

// Define your API interface
interface ProgressApi {
    @GET("your-endpoint")
    suspend fun getProgress(): retrofit2.Response<ProgressResponse>
}

Step 3: Access the Data

Once you fetch the data, you can access it exactly like you requested using bracket notation:

// Fetch the response
val response = progressApi.getProgress()
if (response.isSuccessful) {
    val progressData = response.body()?.progress
    
    // Access course by ID
    val course = progressData?.get("5db0291a02306577129dc153")
    
    // Access module by ID using bracket notation
    val module = course?.get("5db028ab02306577129dc0c6")
    
    // Get the module's progress
    val moduleProgress = module?.progress // Will be 100 in your example
}

Java Equivalent (If Needed)

If you're using Java, here's how the classes would look:

Module Class

public class Module {
    private String name;
    private String type;
    private int progress;

    // Getters and setters (or use Lombok for brevity)
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getType() { return type; }
    public void setType(String type) { this.type = type; }
    public int getProgress() { return progress; }
    public void setProgress(int progress) { this.progress = progress; }
}

Course Class

import com.google.gson.annotations.JsonAnyGetter;
import com.google.gson.annotations.JsonAnySetter;
import com.google.gson.annotations.JsonIgnore;
import java.util.HashMap;
import java.util.Map;

public class Course {
    private String name;
    private String type;
    private double progress;
    
    @JsonIgnore
    private Map<String, Module> modules = new HashMap<>();

    // Getters and setters for fixed fields
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getType() { return type; }
    public void setType(String type) { this.type = type; }
    public double getProgress() { return progress; }
    public void setProgress(double progress) { this.progress = progress; }

    @JsonAnySetter
    public void setModule(String moduleId, Module module) {
        modules.put(moduleId, module);
    }

    @JsonAnyGetter
    public Map<String, Module> getModules() {
        return modules;
    }

    // Convenience method for bracket-like access (Java doesn't support operator overloading)
    public Module getModule(String moduleId) {
        return modules.get(moduleId);
    }
}

Response Class

import com.google.gson.annotations.SerializedName;
import java.util.Map;

public class ProgressResponse {
    @SerializedName("progress")
    private Map<String, Course> progress;

    public Map<String, Course> getProgress() { return progress; }
    public void setProgress(Map<String, Course> progress) { this.progress = progress; }
}

Accessing in Java:

ProgressResponse response = progressApi.getProgress().body();
if (response != null) {
    Map<String, Course> progressData = response.getProgress();
    Course course = progressData.get("5db0291a02306577129dc153");
    Module module = course.getModule("5db028ab02306577129dc0c6");
    int moduleProgress = module.getProgress();
}

Key Notes

  • The @JsonAnySetter annotation captures all fields in the course object that aren't explicitly defined (the dynamic module IDs) and stores them in the modules map.
  • Using a Map<String, Course> for the top-level progress field lets you directly access courses by their ID string.
  • The operator method in Kotlin enables the exact bracket notation you wanted (course["moduleId"]), while Java uses a getter method as it doesn't support operator overloading.

内容的提问来源于stack exchange,提问作者Zubair Zahoor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:05:31