Leave a rating/review
Notes: 05. Parse Data as JSON
The student materials have been reviewed and are updated as of July 2022.
As you have seen, the response you received from the server was in the JSON format. And to show it to the user, you need to parse the message from the JSON object.
Parsing is the process of extracting data from one format, and translating it to another. In your case, you’ll parse from the JSON format, to a Kotlin object.
Before parsing the JSON, however, you need to understand how the JSON structure correlates to Kotlin code. Properties in JSON are wrapped in String literals, and every structure can have as many properties as it needs. You can have Strings, Numbers, Booleans or other structres in JSON.
There are two structures in a JSON object.
Objects, which are marked with curly braces, and collections, marked with square brackets. Both the object and the collection can have other objects or collections as their members, creating nested structures.
As such, an object in JSON correlates to an Object in Kotlin. And a collection correlates to either an array or a list. The named properties in JSON correlate to named properties in Kotlin objects, and have to match their given name, and their type.
So a String in a JSON object can translate into a String in a Kotlin object, if the properties have the same name.
So if you had the following JSON, it would translate into the following Kotlin object.
And even though the Kotlin object doesn’t have all the fields from the JSON object, when parsing, only the things that match will be parsed.
If something is missing in JSON, it will translate to null in Kotlin, and if something is missing in Kotlin, it won’t get parsed.
There are caveats when parsing from JSON to Kotlin, depending on the parser you’re using and the way you write Kotlin. But for now, it’s important that you understand these basics, to be able to apply their principles.
Going back to the project, now that you’ve registered a user, you can log in to the app, to view the data. Let’s see how to implement the login API call, and then use a manual way of parsing data, from a JSON object, to Kotlin data types.
First off, open LoginActivity.kt. Now add the following code to the top:
private val networkStatusChecker by lazy {
NetworkStatusChecker(getSystemService(ConnectivityManager::class.java))
}
Just like before, you’ll check the network conditions. Then add the following code to make sure the API call is done when there’s an Internet connection, and that the UI is updated from the main thread:
networkStatusChecker.performIfConnectedToInternet {
runOnUiThread {
...
}
}
Now head over to the RemoteApi.kt. You can copy most of the base structure for the login call, from the register call, but change the endpoint path:
Thread(Runnable {
val connection = URL("$BASE_URL/api/login").openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("Accept", "application/json")
connection.readTimeout = 10000
connection.connectTimeout = 10000
connection.doOutput = true
connection.doInput = true
connection.disconnect()
}).start()
Then once again format the data in a string, and turn it to a ByteArray:
Thread(Runnable {
val connection = URL("$BASE_URL/api/login").openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("Accept", "application/json")
connection.readTimeout = 10000
connection.connectTimeout = 10000
connection.doOutput = true
connection.doInput = true
val body = "{\"email\":\"${userDataRequest.email}\", " + "\"password\":\"${userDataRequest.password}\"}"
val bytes = body.toByteArray()
connection.disconnect()
}).start()
Next, send the login data, as before, and read the response, as you did with the register call:
Thread(Runnable {
val connection = URL("$BASE_URL/api/login").openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("Accept", "application/json")
connection.readTimeout = 10000
connection.connectTimeout = 10000
connection.doOutput = true
connection.doInput = true
val body = "{\"email\":\"${userDataRequest.email}\", " + "\"password\":\"${userDataRequest.password}\"}"
val bytes = body.toByteArray()
try {
connection.outputStream.use { outputStream ->
outputStream.write(bytes)
}
val reader = InputStreamReader(connection.inputStream)
reader.use { input ->
val response = StringBuilder()
val bufferedReader = BufferedReader(input)
bufferedReader.useLines { lines ->
lines.forEach {
response.append(it.trim())
}
}
}
} catch (error: Throwable) {
}
connection.disconnect()
}).start()
Finally, let’s parse the response. This time, instead of using the plain old string, we need to access the token property within.
Use the JSONObject, to parse the token:
Thread(Runnable {
val connection = URL("$BASE_URL/api/login").openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("Accept", "application/json")
connection.readTimeout = 10000
connection.connectTimeout = 10000
connection.doOutput = true
connection.doInput = true
val body = "{\"email\":\"${userDataRequest.email}\", " + "\"password\":\"${userDataRequest.password}\"}"
val bytes = body.toByteArray()
try {
connection.outputStream.use { outputStream ->
outputStream.write(bytes)
}
val reader = InputStreamReader(connection.inputStream)
reader.use { input ->
val response = StringBuilder()
val bufferedReader = BufferedReader(input)
bufferedReader.useLines { lines ->
lines.forEach {
response.append(it.trim())
}
}
val jsonObject = JSONObject(response.toString())
onUserLoggedIn(jsonObject.getString("token"), null)
}
} catch (error: Throwable) {
onUserLoggedIn(null, error)
}
connection.disconnect()
}).start()
And that’s it! you’re creating a JSON object from the string, and then selecting the "token" field from it, to send to the callback. Run the project, and try to log in, with the user you’ve previously created!
You’ve made it to the main screen! Good job! :]
You might be wondering what the token is. Usually, remote services give you a token, after logging in, as a way to authorize you to some of its API calls and endpoints.
Then when you try to access some API, like the addNote endpoint, you have to send your token, for the server to recognize that you’re able to do it.
If you don’t have a valid token, you won’t be allowed to use those services, as you’ll be unauthorized. The tokens usually hold some encrypted information, like the userId, email, access rights, and so on. The server decrypts these pieces of information, and uses them to verify you!
Let’s go back to the RemoteApi again. Looking at the login api call, the code to format the JSON is really hard to read.
Luckily, there’s an easier way to deal with this problem. Instead of using a string with escaped values, you can use the JSON object instead:
val requestJson = JSONObject()
requestJson.put("email", userDataRequest.email)
requestJson.put("password", userDataRequest.password)
val body = requestJson.toString()
Do the same for the register call:
val requestJson = JSONObject()
requestJson.put("email", userDataRequest.email)
requestJson.put("password", userDataRequest.password)
val body = requestJson.toString()
Also add the following code, to parse the register response:
val jsonObject = JSONObject(response.toString())
onUserCreated(jsonObject.getString("message"), null)
Run the project once again, create a new user, and log in!
Awesome! You’ve learned a quite useful concept of parsing. You’ll practice it, in the next episode!