Parsing JSON Data With Gson in Android
Android applications commonly receive JSON from REST APIs, background services, and local data files. Gson converts these responses into Kotlin or Java model objects, removing the need to retrieve each field manually. This is particularly useful for activity feeds, transport alerts, account dashboards, and shopping applications where the response structure may contain several object types.
This guide uses a Kotlin example that displays activities across Sydney, Melbourne, and Brisbane. It covers model creation, deserialisation, nested collections, Retrofit integration, error handling, and testing. Australian details such as public transport cards, GST-inclusive prices, local time zones, and privacy requirements are included where they affect the implementation.
Adding Gson to an Android Project
Gson is distributed as a Gradle dependency. Add it to the application module rather than the project-level build file, allowing different modules to use an appropriate version independently.
dependencies {
implementation("com.google.code.gson:gson:2.11.0")
}
After synchronising the project, import Gson, GsonBuilder, JsonParser, and the annotation classes as required. Parsing itself needs no Android permission. Add the INTERNET permission only when the application retrieves the JSON from a remote server, and perform network and parsing work away from the main thread.
Mapping JSON Fields to Kotlin Models
A model should represent the response schema rather than the exact layout of a screen. Annotations connect JavaScript property names to readable Kotlin properties, while nullable types accommodate missing or unexpected values.
import com.google.gson.annotations.SerializedName
import java.math.BigDecimal
data class ActivityItem(
@SerializedName("activity_id") val activityId: String?,
val title: String?,
val city: String?,
@SerializedName("price_aud") val priceAud: BigDecimal?,
val available: Boolean?
)
data class ActivityFeed(
val activities: List<ActivityItem>?
)
Gson can create these objects without calling the Kotlin constructor, so nullable fields provide safer handling when a server omits a property. Unknown fields are ignored by default, which helps an application continue working when a backend adds new properties. Use @SerializedName whenever the API uses snake_case, abbreviated names, or names that would otherwise be ambiguous in Kotlin.
Reading and Validating a JSON Response
Gson.fromJson converts a complete JSON document into a model. This direct approach works well when the response has a known top-level structure, such as an object containing an activities array.
val gson = Gson()
val feed = gson.fromJson(responseBody, ActivityFeed::class.java)
val activities = feed?.activities.orEmpty()
An empty list and a failed response represent different conditions. A valid feed with no activities might mean there are no results, whereas malformed JSON indicates that the service or transport disruption feed could not be interpreted. Catch JsonParseException and related Gson exceptions at the data boundary, record a safe diagnostic message, and present an error state instead of silently replacing invalid data with an empty result.
Parsing Nested Objects and Collections
Nested arrays require the expected generic type because Gson needs to know which model should be created for each element. TypeToken preserves that information when deserialising a list directly.
val root = JsonParser.parseString(responseBody).asJsonObject
val alertsElement = root.getAsJsonArray("alerts")
val alerts: List<TransitAlert> = gson.fromJson(
alertsElement,
object : TypeToken<List<TransitAlert>>() {}.type
)
Check that the root and nested elements have the expected JSON types before calling methods such as asJsonObject or asJsonArray. For dates, retain an ISO 8601 value in the model and convert it with java.time, using a zone such as Australia/Sydney or Australia/Melbourne. Core library desugaring makes java.time available on older supported Android versions. Prices should use BigDecimal and be formatted for Australian dollars with Locale("en", "AU"); the display can also state whether the amount includes GST.
Connecting Gson to Retrofit
Retrofit can use a shared Gson instance through its Gson converter factory. This keeps the same model mappings, date handling, and null behaviour across direct parsing and network responses.
val gson = GsonBuilder().create()
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
Create an API interface whose suspend function returns the feed model, then call it from a lifecycle-aware coroutine. The base URL must end with a slash, and the JSON converter dependency must be included in the Gradle configuration.
Separate transport failures from invalid data by checking the HTTP response before deserialising it. A 404, rate-limit response, or server error should not be parsed as an activity feed. On a transport screen, a floating action button guide shows how to place a retry action without covering important alerts. Represent loading, populated, empty, and failed states explicitly so commuters are not told there are no disruptions when the request actually failed.
Testing, Privacy and Release Readiness
Local unit tests should parse representative fixtures and assert the mapped values rather than merely checking that no exception occurred. Useful cases include a normal Melbourne response, a Brisbane feed with a missing optional field, an empty collection, malformed JSON, and an unexpected data type. These fixtures resemble the JSON from the real service without containing customer information.
Release builds also need attention to shrinking and obfuscation. R8 can remove generic signatures needed for collection mapping, so apply @Keep to important models or configure suitable keep rules. Exercise the same parser against the release build to catch issues that do not appear during local testing.
Australian privacy obligations are equally important when responses contain account, location, or order details. For covered organisations, handling personal information must align with the Privacy Act 1988 and the Australian Privacy Principles. Never log supermarket delivery addresses, rideshare locations, or identifiers associated with Opal, Myki, or Go Card services. These details can appear in everyday Android applications, yet they should be redacted before logging or analytics. A reliable parser therefore separates schema validation, network state, sensitive-data handling, and presentation while producing predictable models for Australian users.