8.3 JSON and Moshi

What is a JSON?

JSON, or JavaScript Object Notation, is a way of representing data. While its syntax comes from JavaScript, it is still language independent.

When it comes to networking, JSON is typically used in two cases. One is to POST data to a server. The other is a server’s response to the client. Servers may choose to use other formats, like XML, so don’t assume that every server will use JSON (but it is the most common type of format).

There are two main advantages when using JSON with networking. It's very easy for a client to parse the file. Also, it is easily formatted in a way that’s easy for us humans to read.

Above is a sample of JSON data (if you're familiar with python, it looks very similar to a dictionary).

The data consists of pairs of keys and values. There is a colon character separating each key and value. In the example, base is a key, and GBP is the corresponding value. A comma separates each pair.

A value can be a string in double quotes, or a number, or true or false or null, or another object or an array. These structures can be nested (e.g. I can have an array be a list of objects).

Information within curly braces is an object. In the example above, the value for the rates key is another object, containing more pairs.

Above is an example of a JSON array.

See how this starts and ends with square brackets? This indicates that the data is an array. Each object is a separate ‘record’ or element in the array. Each object in the array is also separated by a comma.

For the sake of example, the array is a list of objects but the array can be a list of any of the valid JSON types (you can also have arrays of many different types).

From this you can kind of see how tedious it would be to try and iterate through an array like this to parse the JSON objects (which is where Moshi comes into play to make this process easier for us)!

Using Moshi

Many times what's returned from networking calls are class objects represented as JSONs, which can be a headache to try and parse manually (and often times isn't as efficient). In comes Moshi, which is a handy library also developed by Square used for converting between JSON and Kotlin objects. In tandem they make networking in Android super easy!

Setting up

In a language like python, we would need to pip install x and then later import x but in Kotlin we have a much more complicated file structure and system so we do the following (typically):

In the build.gradle we have to add dependencies to "install" the library

Add the following dependencies in your module-level build.gradle file:

// Plugins should be located at the very top of your file
apply plugin: 'kotlin-kapt'

// Alternative if your plugins look like this!
plugins {
   ...
   id 'kotlin-kapt' 
}

...

dependencies {
   implementation("com.squareup.moshi:moshi-kotlin:1.13.0")
   kapt("com.squareup.moshi:moshi-kotlin-codegen:1.13.0")
}

We're gonna be referring to the classes below as we introduce Moshi:

@JsonClass(generateAdapter = true) // Data classes MUST have this line right above
data class BlackjackHand(
  val hidden_card: Card,
  val visible_cards: List<Card>,
  ...
)

@JsonClass(generateAdapter = true)
data class Card(
  val rank: Char,
  val suit: Suit
  ...
)

@Keep
enum class Suit {
  CLUBS, DIAMONDS, HEARTS, SPADES;
}

Moshi gives us JSONs like this that we can read from and write to:

{
  "hidden_card": {
    "rank": "6",
    "suit": "SPADES"
  },
  "visible_cards": [
    {
      "rank": "4",
      "suit": "CLUBS"
    },
    {
      "rank": "A",
      "suit": "HEARTS"
    }
  ]
}

Pay special note that the names of our fields in our data class should correspond to what we expect to receive from our response. Check out this section to see how we can change that!

Converting JSON -> Kotlin

If we want to convert JSONs into objects of our Kotlin class, we can do something like what's below:

val json: String = ... // JSON from our response

val moshi: Moshi = Moshi.Builder().build()
val jsonAdapter: JsonAdapter<BlackjackHand> = 
    moshi.adapter<BlackjackHand>(BlackjackHand::class.java)

// fromJson(...) convers the JSON into a BlackjackHand
val blackjackHand: BlackjackHand = jsonAdapter.fromJson(json)

Converting Kotlin -> JSON

If we want to go the other way around:

val blackjackHand = BlackjackHand(
    Card('6', SPADES),
    listOf(Card('4', CLUBS), Card('A', HEARTS))
  )

val moshi: Moshi = Moshi.Builder().build()
val jsonAdapter: JsonAdapter<BlackjackHand> = 
    moshi.adapter<BlackjackHand>(BlackjackHand::class.java)

// JSON that can be used in a POST request body, etc.
val json: String = jsonAdapter.toJson(blackjackHand)

Moshi has built-in support for reading and writing Kotlin's core data types:

  • Primitives (int, float, char...)

  • Arrays, Collections, Lists, Sets, and Maps

  • Strings

  • Enums

Parse JSON Arrays

Say we have a JSON string of this structure:

[
  {
    "rank": "4",
    "suit": "CLUBS"
  },
  {
    "rank": "A",
    "suit": "HEARTS"
  }
]

We can now use Moshi to parse the JSON string into a List<Card>:

val cardArrayJsonResponse: String = ...


// Creates a List<Card> type to adapt from
val type = Types.newParameterizedType(
    List::class.java, //List:class.java must come first!
    Card::class.java
)

val adapter = moshi.adapter<List<Card>>(type)
val cards: List<Card> = adapter.fromJson(cardsJsonResponse)

Custom field names with @Json

Moshi works best when your JSON objects and Java or Kotlin classes have the same structure. But when they don't, Moshi has annotations to customize data binding.

Use @Json to specify how Kotlin properties map to JSON names. This is necessary when the JSON name contains spaces or other characters that aren’t permitted in Kotlin property names. For example, this JSON has a field name containing a space:

{
  "username": "jesse",
  "lucky number": 32
}

We can't have spaces in our Kotlin property names, so we can create a mapping of how Moshi should convert ourselves:

data class Player(
  val username: String
  @Json(name = "lucky number") val luckyNumber: Int

  ...
)

Last updated