Question
Given this Java POJO that uses Gson annotations:
class Topic {
@SerializedName("id")
long id;
@SerializedName("name")
String name;
}
How can you specify JSON key names for properties in a Kotlin data class, equivalent to placing @SerializedName on Java fields?
data class Topic(
val id: Long,
val name: String
)
Short Answer
You will learn how Gson maps JSON fields to Kotlin properties, how to apply @SerializedName correctly with @field:, and when annotations are unnecessary because property and JSON names already match.
Concept
Gson converts JSON object keys into fields on a Kotlin or Java object. By default, a JSON key must match the field name:
{
"id": 42,
"name": "Kotlin"
}
This JSON already matches this Kotlin class:
data class Topic(
val id: Long,
val name: String
)
No annotation is needed in that case.
Use @SerializedName when the JSON key differs from the Kotlin property name. For example, an API may return topic_id, while your Kotlin code should use the clearer name id.
Gson is primarily field-based. A Kotlin property can produce a constructor parameter, a property, a getter, and a backing field. The explicit @field: use-site target places the annotation on the backing field, which is the location Gson reads when mapping fields.
Mental Model
Think of JSON keys as labels on delivery boxes and Kotlin properties as labelled shelves.
- If the box is labelled
nameand the shelf is labelledname, Gson knows where it belongs. - If the box is labelled
topic_namebut the shelf is labelledname, Gson needs a translation label. @field:SerializedName("topic_name")is that translation label placed on the exact shelf Gson checks.
Syntax and Examples
Import Gson's annotation and apply it to the backing field with @field:SerializedName:
import com.google.gson.annotations.SerializedName
data class Topic(
@field:SerializedName("topic_id")
val id: Long,
@field:SerializedName("topic_name")
val name: String
)
Given this JSON:
{
"topic_id": 101,
"topic_name": "Android"
}
Gson maps it as follows:
topic_idbecomesid.topic_namebecomesname.
Deserialize it with Gson:
import com.google.gson.Gson
val json = """{"topic_id":101,"topic_name":"Android"}"""
val topic = Gson().fromJson(json, Topic::class.java)
println(topic.id) // 101
println(topic.name) // Android
If the JSON keys are already id and name, keep the class simple:
data class Topic(
val id: Long,
val name: String
)
Step by Step Execution
Consider this JSON and model:
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
data class Topic(
@field:SerializedName("topic_id")
val id: Long,
@field:SerializedName("title")
val name: String
)
val json = """{"topic_id":7,"title":"Networking"}"""
val topic = Gson().fromJson(json, Topic::class.java)
Step by step:
- Gson reads the JSON object.
- It finds the
topic_idkey with the value7. - It inspects the
idfield and sees@SerializedName("topic_id"). - Gson assigns
7totopic.id. - Gson finds the
titlekey with the value"Networking". - The annotation on
nametells Gson thattitlemaps toname. - The resulting value is equivalent to:
Topic(id = 7, name = "Networking")
topic.nameprintsNetworking.
Real World Use Cases
@SerializedName is useful whenever an external JSON format should not dictate names throughout your Kotlin code.
- Snake case APIs: Map
created_attocreatedAt. - Legacy APIs: Map inconsistent names such as
usr_nmtouserName. - Reserved or awkward names: Map JSON
defaultto a safer Kotlin name such asisDefault. - API migrations: Accept an old API key while keeping the app's existing property name.
- Readable domain models: Keep Kotlin names clear even if a third-party API uses abbreviations.
Example:
data class UserProfile(
@field:SerializedName("user_id")
val userId: Long,
@field:SerializedName("created_at")
val createdAt: String,
@field:SerializedName("is_active")
val isActive: Boolean
)
Real Codebase Usage
In real projects, developers usually keep API models separate from app domain models.
data class TopicResponse(
@field:SerializedName("topic_id")
val id: Long,
@field:SerializedName("topic_name")
val name: String
)
data class Topic(
val id: Long,
val name: String
)
fun TopicResponse.toDomain(): Topic = Topic(
id = id,
name = name
)
This pattern helps because:
- Gson annotations stay in the networking layer.
- The rest of the application does not depend on a particular API's naming choices.
- API changes are easier to isolate.
For optional API fields, use nullable types when a value may be absent:
data class TopicResponse(
@field:SerializedName("description")
val description: String?
)
Validate required values before using them in business logic, rather than assuming every server response is complete.
Common Mistakes
Forgetting the import
SerializedName comes from Gson:
import com.google.gson.annotations.SerializedName
Omitting @field: when you need field-based mapping
This may appear to work in some situations, but it is less explicit about where Kotlin places the annotation:
// Avoid relying on an implicit annotation target.
data class Topic(
@SerializedName("topic_id")
val id: Long
)
Use the field target for Gson field mapping:
data class Topic(
@field:SerializedName("topic_id")
val id: Long
)
Annotating when names already match
This is valid but redundant:
data class Topic(
@field:SerializedName("id")
val id: Long
)
Prefer no annotation unless you need a different JSON name, an alternate name, or documentation value.
Using a non-null type for a field the server may omit
If an API can omit name or send null, model that possibility:
data class Topic(
@field:SerializedName("name")
val name: String?
)
Then handle null deliberately in your UI or business logic.
Expecting Gson to use Kotlin default constructor values
Gson behavior with Kotlin defaults can be surprising because Gson does not always construct Kotlin classes through the primary constructor. Do not rely on default values as your only validation strategy for untrusted API responses.
Comparisons
| Situation | Best approach |
|---|---|
| JSON key and Kotlin property have the same name | No annotation needed |
JSON uses a different key, such as user_id | @field:SerializedName("user_id") |
| JSON may contain multiple accepted names | Use alternate with @SerializedName |
A JSON value may be missing or null | Use a nullable Kotlin type when appropriate |
| You want one naming rule for many fields | Configure Gson with a field naming policy |
An alternate-name example:
data class Topic(
@field:SerializedName(value = "name", alternate = ["topic_name", "title"])
val name: String
)
For serialization, Gson uses the main value (name). During deserialization, it can accept name, topic_name, or title.
A naming policy can reduce repeated annotations when every API field follows the same convention:
import com.google.gson.FieldNamingPolicy
import com.google.gson.GsonBuilder
val gson = GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()
With this configuration, createdAt maps to created_at automatically.
Cheat Sheet
import com.google.gson.annotations.SerializedName
data class Topic(
@field:SerializedName("topic_id")
val id: Long,
@field:SerializedName("topic_name")
val name: String
)
- Use
@field:SerializedName("json_key")for Gson field mapping in Kotlin. - Do not annotate properties when JSON and Kotlin names already match.
valueis the primary JSON name used when Gson writes JSON.alternatelists extra names Gson can accept while reading JSON.- Use nullable types such as
String?for genuinely optional or nullable API fields. - A Gson
FieldNamingPolicyis useful when many keys follow the same naming convention.
@field:SerializedName(value = "name", alternate = ["title"])
val name: String
FAQ
Do Kotlin data classes work with Gson?
Yes. Gson can deserialize JSON into Kotlin data classes. Use @field:SerializedName when a JSON key differs from the property name.
Do I need @SerializedName for every Kotlin property?
No. It is unnecessary when the JSON key exactly matches the property name.
Why use @field:SerializedName instead of only @SerializedName?
Kotlin properties can have several generated targets. @field: explicitly places the annotation on the backing field, which matches Gson's common field-based mapping approach.
Can Gson map snake_case JSON to camelCase Kotlin properties?
Yes. Add @field:SerializedName("user_name") to userName, or configure Gson with FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES.
Can one Kotlin property accept old and new JSON keys?
Yes. Use alternate:
@field:SerializedName(value = "name", alternate = ["topic_name"])
val name: String
What happens if JSON does not contain a non-null Kotlin property?
Treat the response carefully. If a field is optional in the API, make its type nullable or validate the parsed model before relying on it.
Does @SerializedName change the Kotlin property name?
No. Your Kotlin code still uses the property name, such as topic.name. The annotation only controls the JSON name Gson maps.
Mini Project
Description
Build a small API response model for a topic list. The simulated API uses snake_case keys, while the Kotlin app uses camelCase property names. This demonstrates practical Gson field mapping and nullable optional fields.
Goal
Deserialize a JSON topic response into Kotlin data classes and print the mapped values.
Requirements
Create a TopicResponse data class with JSON key annotations.
Use @field:SerializedName for JSON keys that differ from Kotlin property names.
Include an optional nullable description field.
Deserialize a JSON string with Gson.
Print the ID, name, and description of each topic.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.