Wednesday, July 17, 2019

Kotlin in Action: Answers to Chapter 5 Exercises

So here are the answers to the last post.

Exercise 1: Json Manipulation

import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser

fun main() {
  val usersJson = """[
                    |  {
                    |    "id": "543",
                    |    "username": "john123",
                    |    "firstName": "John",
                    |    "lastName": "Smith",
                    |    "email": "john@smith.com"
                    |  },   {
                    |    "id": "438",
                    |    "username": "janedoe5",
                    |    "firstName": "Jane",
                    |    "lastName": "Doe",
                    |    "email": "jane.doe@gmail.com"
                    |  }
                    |]""".trimMargin()
  val booksJson = """[
                    |  {"id": "1", "title": "Kotlin in Action"},
                    |  {"id": "2", "title": "Kotlin in Action"},
                    |  {"id": "3", "title": "Learning RxJava"},
                    |  {"id": "4", "title": "Refactoring"},
                    |  {"id": "5", "title": "Grokking Algorithms"},
                    |  {"id": "6", "title": "Code Complete"}
                    |]""".trimMargin()
  val checkoutsJson = """[
                        |  {"userId": "543", "bookId": "1"},
                        |  {"userId": "543", "bookId": "5"},
                        |  {"userId": "438", "bookId": "2"},
                        |  {"userId": "438", "bookId": "3"}
                        |]""".trimMargin()

  val parser = JsonParser()
  val users = parser.parse(usersJson).asJsonArray
  val books = parser.parse(booksJson).asJsonArray
  val checkouts = parser.parse(checkoutsJson).asJsonArray

  val userCheckouts = users.map { it.asJsonObject }
    .flatMap { user -> checkouts.map { it.asJsonObject }
      .filter { checkout -> user["id"] == checkout["userId"] }
      .map { checkout -> JsonObject().apply {
        addProperty("firstName", user["firstName"].asString)
        addProperty("lastName", user["lastName"].asString)
        addProperty("bookId", checkout["bookId"].asString)
      } }
    }
    .flatMap { userCheckout -> books.map { it.asJsonObject }
      .filter { book -> userCheckout["bookId"] == book["id"] }
      .map { book -> userCheckout.deepCopy().apply {
        addProperty("title", book["title"].asString)
        remove("bookId")
      } }
    }
    .fold(JsonArray()) { array, obj -> array.apply { add(obj) }}
  println(userCheckouts)
}
Now honestly the java code wasn't very clean and readable to start with, so if we wanted to clean it up a little we could do the following:
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser

fun main() {
  val usersJson = """[
                    |  {
                    |    "id": "543",
                    |    "username": "john123",
                    |    "firstName": "John",
                    |    "lastName": "Smith",
                    |    "email": "john@smith.com"
                    |  },   {
                    |    "id": "438",
                    |    "username": "janedoe5",
                    |    "firstName": "Jane",
                    |    "lastName": "Doe",
                    |    "email": "jane.doe@gmail.com"
                    |  }
                    |]""".trimMargin()
  val booksJson = """[
                    |  {"id": "1", "title": "Kotlin in Action"},
                    |  {"id": "2", "title": "Kotlin in Action"},
                    |  {"id": "3", "title": "Learning RxJava"},
                    |  {"id": "4", "title": "Refactoring"},
                    |  {"id": "5", "title": "Grokking Algorithms"},
                    |  {"id": "6", "title": "Code Complete"}
                    |]""".trimMargin()
  val checkoutsJson = """[
                        |  {"userId": "543", "bookId": "1"},
                        |  {"userId": "543", "bookId": "5"},
                        |  {"userId": "438", "bookId": "2"},
                        |  {"userId": "438", "bookId": "3"}
                        |]""".trimMargin()

  val parser = JsonParser()
  val users = parser.parse(usersJson).asJsonArray
  val books = parser.parse(booksJson).asJsonArray
  val checkouts = parser.parse(checkoutsJson).asJsonArray

  val userCheckouts = users.map { it.asJsonObject }
    .flatMap { user -> findUserCheckouts(user, checkouts)
      .map { checkout -> buildUserCheckout(user, checkout) }
    }
    .flatMap { userCheckout -> findCheckoutBooks(userCheckout, books)
      .map { book -> swapBookIdForTitleOnUserCheckout(userCheckout, book) }
    }
    .toJsonArray()
  println(userCheckouts)
}

private fun findUserCheckouts(
  user: JsonObject,
  checkouts: JsonArray
): List =
  checkouts.map { it.asJsonObject }
    .filter { checkout -> user["id"] == checkout["userId"] }

private fun buildUserCheckout(
  user: JsonObject,
  checkout: JsonObject
): JsonObject =
  JsonObject().apply {
    addProperty("firstName", user["firstName"].asString)
    addProperty("lastName", user["lastName"].asString)
    addProperty("bookId", checkout["bookId"].asString)
  }

private fun findCheckoutBooks(
  userCheckout: JsonObject,
  books: JsonArray
): List =
  books.map { it.asJsonObject }
    .filter { book -> userCheckout["bookId"] == book["id"] }

private fun swapBookIdForTitleOnUserCheckout(
  userCheckout: JsonObject,
  book: JsonObject
): JsonObject =
  userCheckout.deepCopy().apply {
    addProperty("title", book["title"].asString)
  }

private fun List<JsonObject>.toJsonArray(): JsonArray =
    fold(JsonArray()) { array, obj -> array.apply { add(obj) }}