Table of Contents
Why JSON Matters in Android Apps
Many Android apps talk to web servers to get data. In most modern APIs, that data is sent as JSON. JSON stands for JavaScript Object Notation. It is a simple text format that represents objects, lists, strings, numbers, booleans, and null.
Understanding JSON parsing means you can take raw JSON text from a server and turn it into Kotlin objects that your app can use. In this chapter, we focus on JSON content itself and the parsing concepts. Actual networking with libraries such as Retrofit is handled later.
JSON is language independent. The server can be written in any language, and your Android app can still understand the data as long as it follows valid JSON format.
JSON is always text. To use it in your app, you must parse it into objects or collections. Never try to work directly on raw JSON strings for structured data.
Basic JSON Structure
JSON data is built from two main structures: objects and arrays.
A JSON object is an unordered collection of key value pairs. In text form, it is enclosed in {} and each key is a string. For example:
{
"id": 1,
"name": "Alice",
"isActive": true
}
A JSON array is an ordered list of values, enclosed in []. For example:
[
10,
20,
30
]Objects and arrays can be nested. A small user list might look like this:
{
"users": [
{
"id": 1,
"name": "Alice",
"isActive": true
},
{
"id": 2,
"name": "Bob",
"isActive": false
}
],
"count": 2
}
Valid JSON values can be one of these types: object, array, string, number, boolean, or null. Keys in objects must always be strings.
JSON vs Kotlin Types
When you parse JSON in Android, you convert JSON values into Kotlin types. In simple cases, the mapping is straightforward.
A typical mapping looks like this:
- JSON string becomes
String - JSON number becomes
Int,Long,Float, orDouble - JSON boolean becomes
Boolean - JSON null becomes
nullreference in Kotlin - JSON object becomes a Kotlin class instance, often a data class
- JSON array becomes a Kotlin
List<T>orArray<T>
For example, suppose you receive this JSON:
{
"id": 42,
"title": "Hello",
"rating": 4.5,
"tags": ["welcome", "example"],
"author": {
"name": "Sam",
"age": 30
}
}A matching Kotlin model might look like this:
data class Author(
val name: String,
val age: Int
)
data class Post(
val id: Int,
val title: String,
val rating: Double,
val tags: List<String>,
val author: Author
)This model is not yet parsed code, but it shows the idea that JSON structure and Kotlin types should line up.
Keep JSON field names and Kotlin property expectations in sync. If the server changes field names or types, your parsing can fail or produce incorrect data.
Parsing JSON Manually with JSONObject and JSONArray
Before using advanced libraries, Android offers basic JSON parsing classes in org.json. These are JSONObject for objects and JSONArray for arrays. They work with raw JSON strings.
Suppose you have a JSON string with a single user:
val jsonString = """
{
"id": 1,
"name": "Alice",
"isActive": true
}
""".trimIndent()
You can parse this string into a JSONObject and extract data:
import org.json.JSONObject
val jsonObject = JSONObject(jsonString)
val id = jsonObject.getInt("id")
val name = jsonObject.getString("name")
val isActive = jsonObject.getBoolean("isActive")
Each getXXX method retrieves a value for a given key and converts it to a specific type. If the key is missing or has the wrong type, it will throw an exception.
If you prefer safer access, you can use optXXX methods:
val idOptional = jsonObject.optInt("id", -1)
val nameOptional = jsonObject.optString("name", "")
val isActiveOptional = jsonObject.optBoolean("isActive", false)
The opt methods return a default value if the key is missing or cannot be converted.
For JSON arrays, you use JSONArray:
val jsonArrayString = """
[
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]
""".trimIndent()
val jsonArray = JSONArray(jsonArrayString)
for (i in 0 until jsonArray.length()) {
val userObject = jsonArray.getJSONObject(i)
val id = userObject.getInt("id")
val name = userObject.getString("name")
// Use id and name as needed
}You can also parse objects that contain arrays and nested objects. For example:
val responseString = """
{
"users": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
],
"count": 2
}
""".trimIndent()
val root = JSONObject(responseString)
val count = root.getInt("count")
val usersArray = root.getJSONArray("users")
for (i in 0 until usersArray.length()) {
val user = usersArray.getJSONObject(i)
val id = user.getInt("id")
val name = user.getString("name")
}This manual style gives you full control but can become verbose if your JSON is large or deeply nested.
When using manual parsing, always validate keys and handle missing values. A missing or mis-typed key can crash your app if you use getXXX without checks.
Handling Optional and Null Values
Real APIs are rarely perfect. Some fields can be missing or null. Kotlin uses null safety, and you need to reflect this in your parsing logic.
With JSONObject, you can check if a key exists:
if (jsonObject.has("nickname")) {
val nickname = jsonObject.getString("nickname")
}
If the value might be null, you can use isNull:
if (!jsonObject.isNull("nickname")) {
val nickname = jsonObject.getString("nickname")
}You can combine this with Kotlin nullable types:
val nickname: String? = if (!jsonObject.isNull("nickname")) {
jsonObject.getString("nickname")
} else {
null
}
Or use the opt methods with nullable conversions:
val nickname: String? = jsonObject.optString("nickname", null)
If you later use this value in Kotlin, you must consider that it may be null and handle it accordingly.
Never assume that fields returned from an external API are always present or non null. Design your parsing so that missing or null fields do not crash the app.
Mapping JSON to Kotlin Data Classes
Manual extraction works, but for larger responses it becomes repetitive. A common practice is to map JSON objects to Kotlin data classes so your app works with typed models instead of low level JSONObject instances.
The idea is simple. You define data classes that match the JSON structure and then parse JSON into instances of those classes. The parsing step can be manual or managed by a library.
For a manual example, consider JSON for a single product:
{
"id": 3,
"name": "Phone",
"price": 199.99,
"inStock": true
}You may define this model:
data class Product(
val id: Int,
val name: String,
val price: Double,
val inStock: Boolean
)A basic mapper could look like this:
fun parseProduct(jsonString: String): Product {
val obj = JSONObject(jsonString)
return Product(
id = obj.getInt("id"),
name = obj.getString("name"),
price = obj.getDouble("price"),
inStock = obj.getBoolean("inStock")
)
}If the API returns a list of products:
{
"products": [
{"id": 1, "name": "Phone", "price": 199.99, "inStock": true},
{"id": 2, "name": "Tablet", "price": 299.99, "inStock": false}
]
}You can parse it as follows:
fun parseProductList(jsonString: String): List<Product> {
val root = JSONObject(jsonString)
val array = root.getJSONArray("products")
val result = mutableListOf<Product>()
for (i in 0 until array.length()) {
val item = array.getJSONObject(i)
val product = Product(
id = item.getInt("id"),
name = item.getString("name"),
price = item.getDouble("price"),
inStock = item.getBoolean("inStock")
)
result.add(product)
}
return result
}
This pattern separates parsing logic from the rest of your app. Other parts of the code only work with Product and do not care about raw JSON.
Dealing with Type Mismatches and Errors
Parsing JSON is not always smooth. Servers can return unexpected values, or network issues can corrupt responses. Your parsing logic should be defensive.
A common practice is to wrap parsing in try and catch blocks:
fun safeParseUser(jsonString: String): User? {
return try {
val obj = JSONObject(jsonString)
User(
id = obj.getInt("id"),
name = obj.getString("name")
)
} catch (e: Exception) {
null
}
}You can also apply partial fallbacks:
fun parseUserWithFallback(jsonString: String): User {
val obj = JSONObject(jsonString)
val id = obj.optInt("id", -1)
val name = obj.optString("name", "Unknown")
val age = if (!obj.isNull("age")) obj.optInt("age") else 0
return User(id = id, name = name, age = age)
}
In many apps, you combine both ideas. You use opt methods to avoid crashes from missing fields, and you handle serious errors, such as completely invalid JSON, with a try and catch.
Never trust external JSON to have the exact type you expect. Use safe access methods, validate values, and catch parsing exceptions to keep your app stable.
JSON Parsing Libraries Overview
While the org.json classes are enough to understand basic parsing, most real apps use dedicated JSON libraries. These tools can automatically map JSON to Kotlin data classes and handle most parsing work for you.
Popular libraries in Android include Gson, Moshi, and Kotlinx serialization. They follow similar ideas. You define data classes that represent your JSON, then call a function that converts a JSON string into an instance of that class.
For example, with such a library, pseudocode might look like:
data class User(
val id: Int,
val name: String
)
// Somewhere in your code
val user: User = jsonParser.fromJson(jsonString, User::class.java)Each library has its own setup, annotations, and advanced features, such as customizing field names, handling default values, and ignoring unknown fields. These details are covered when you integrate parsing with networking and Retrofit.
The main concept to remember is that these libraries still follow the same mapping rules between JSON values and Kotlin types. The benefit is less manual code and fewer parsing errors when your models are well defined.
JSON in Networking Workflows
JSON parsing is usually part of a bigger process. Your app sends an HTTP request, receives a JSON response, and then parses that JSON for display or further logic.
The workflow often looks like this.
You perform a network call. You get the response body as a JSON string. Then you parse that JSON into your model classes. Later, you update the UI based on the parsed data.
In later chapters that cover Retrofit, you will see how network libraries can combine HTTP requests and JSON parsing so that you directly obtain Kotlin models without manually handling raw JSON strings.
For now, the important part is understanding that JSON is the bridge between your app and the server, and JSON parsing is what turns this text into structured Kotlin data you can safely work with.