Question

Kotlin Data Classes and Gson @SerializedName Annotations

kotlinjavajsongsondata-class

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 name and the shelf is labelled name, Gson knows where it belongs.
  • If the box is labelled topic_name but the shelf is labelled name, Gson needs a translation label.
  • @field:SerializedName("topic_name") is that translation label placed on the exact shelf Gson checks.
Take Quiz

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_id becomes id.
  • topic_name becomes name.

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:

  1. Gson reads the JSON object.
  2. It finds the topic_id key with the value 7.
  3. It inspects the id field and sees @SerializedName("topic_id").
  4. Gson assigns 7 to topic.id.
  5. Gson finds the title key with the value "Networking".
  6. The annotation on name tells Gson that title maps to name.
  7. The resulting value is equivalent to:
Topic(id = 7, name = "Networking")
  1. topic.name prints Networking.

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_at to createdAt.
  • Legacy APIs: Map inconsistent names such as usr_nm to userName.
  • Reserved or awkward names: Map JSON default to a safer Kotlin name such as isDefault.
  • 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
)
Take Quiz

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

SituationBest approach
JSON key and Kotlin property have the same nameNo annotation needed
JSON uses a different key, such as user_id@field:SerializedName("user_id")
JSON may contain multiple accepted namesUse alternate with @SerializedName
A JSON value may be missing or nullUse a nullable Kotlin type when appropriate
You want one naming rule for many fieldsConfigure 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.

Take Quiz

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.
  • value is the primary JSON name used when Gson writes JSON.
  • alternate lists extra names Gson can accept while reading JSON.
  • Use nullable types such as String? for genuinely optional or nullable API fields.
  • A Gson FieldNamingPolicy is 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.

Take Quiz

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.

Take Quiz

Keep learning

Related questions

View more