Yummypets / JSON.kt

Kotlin JSON Parsing that infers type (org.json.JSONObject Wrapper)
MIT License
8 stars 1 forks source link
android decoding json jsonobject kotlin mapping micro-fra type-inference unbox

JSON.kt

Language: Kotlin Platform: Android 8+ codebeat badge License: MIT

Kotlin JSON Parsing that infers type πŸš€ JSON.kt is a kotlin wrapper of java's org.json.JSONObject that exposes a nicer syntax for kotlin code.

car::id < json["id"]
car::name < json["name"]
car::statistics < json["stats", CarStatsJSONMapper()]
car::productionStartDate < json["dates.production_dates.start_date"]

Why?

Because parsing JSON is often full of unecessary if statements, obvious casts and nil-checks We deserve better !

How!

By using a simple < operator that takes care of the boilerplate code for us thanks to beautiful kotlin generics. JSON mapping code becomes concise and maintainable ❀️

Why use JSON.kt

Example

Given a kotlin Model Car

class Car {
    var id = 0
    var name = ""
    var statistics = CarStatistics()
    var productionStartDate = 0
}

And the following JSON file

{
    "id": 265,
    "name": "Ferrari",
    "stats": {
        "maxSpeed": 256,
        "numberOfWheels": 4
    },
    "dates": {
        "production_dates": {
            "start_date": "1984",
            "end_date": "1993"
        }
    }
}

Before with JSONObject 😱

if (jsonObject.has("id")) {
    car.id = jsonObject.getInt("id")
}
if (jsonObject.has("name")) {
    car.name = jsonObject.getString("name")
}
if (jsonObject.has("stats")) {
    val statsParser = CarStatsJSONMapper()
    car.statistics = statsParser.parse(jsonObject.getJSONObject("stats"))
}
if (jsonObject.has("dates")) {
    val dates = jsonObject.getJsonObject("dates")

    if (dates.has("production_dates")) {
        val productionDates = dates.getJsonObject("production_dates")

        if (productionDates.has("start_date")) {
            car.productionStartDate = productionDates.getJsonObject("start_date")
        }
    }
}

With JSON.kt 😎

car::id < json["id"]
car::name < json["name"]
car::statistics < json["stats", CarStatsJSONMapper()]
car::productionStartDate < json["dates.production_dates.start_date"]

The < operator maps a model property with a json key. Notice that this does exactly the same as the old parsing above, meaning that if key does not exist, nothing happens and the model keeps its previous value.

On the third line, we can provide our own custom mapper which enables us to reuse the same mapping at different places. πŸ€“

Usage 🚘

val json = JSON("[aJSONString]") // or JSON(jsonObject)"
val car = CarJSONParser().parse(json)

Providing default values

In the previous examples we used json["key"], here we are going to use json("key") that returns an optional value. Perfect for providing default values while parsing!

car.id = json("id") ?: 0
car.name = json("name") ?: "unknown car"
car.statistics = json("stats", CarStatsJSONMapper()) ?: CarStatistics()
car.productionStartDate = json("dates.production_dates.start_date") ?: 0

Specify the return type

Sometimes it might be necessary to explicitly indicate the return type.

Storing the result in a variable
val id: Int = json("id")
Without storing the result in a variable
json<JSONArray>("images")?.let { imageJsonArray ->
  //Do something
}

A typical parser

class CarJSONParser : JSONParser<Car> {

    @Throws(JSONException::class)
    override fun parse(json: JSON): Car {
        val car = Car()
        car::id < json["id"]
        car::name < json["name"]
        car::statistics < json["stats", CarStatsJSONMapper()]
        car::productionStartDate < json("dates.production_dates.start_date")
        return car
    }
}

Installation

Manual

Just copy-paste JSON.kt file \o/