0

I have a Java object that has methods getLong, getBoolean and getString. I attempted to make a generic extension function that has a function as the last parameter. Essentially wrapping the try and catch and call to getString etc that could throw an exception. I discovered that <reified T> as set when calling for example getIt<Long>() { // do something with it } needs the reflection api to figure out T. I could not do is check nor isInstance. Any ideas?

// This is the non generic function version to get an idea
inline fun JSONObject.getItLong(key: String, block: (value: Long) -> Unit) {
    try {
        block(this.getLong(key))
    } catch (e: JSONException) {
        Log.w("${ javaClass.simpleName }KEx", e.message)
    }
}

The below when does not work.

inline fun <reified T> JSONObject.getIt(key: String, block: (value: T) -> Unit) {
    try {
        when {
            Long is T -> { block(this.getLong(key) as T) }
            String is T -> { block(this.getString(key) as T) }
            // Boolean is T -> { block(this.getBoolean(key) as T) } // Boolean broken, does not have companion object?
        }
    } catch (e: JSONException) {
        Log.w("fetchFromJSONObject", e.message)
    }
}

So, aside from the Boolean issue I wanted to have a generic way to call the right type of get by using T. I ran into needing to add kaitlin reflection jar to the classpath. I wanted to avoid that if possible.

UPDATE1: The first answer and response using a when with T::class does not actually work. Thanks for the idea and it helped me look again. The second I found "wordier" than what I wanted, so I ended up with this solution.

inline fun <reified T> JSONObject.getIt(key: String, block: (value: T) -> Unit) {
    try {
        block(this.get(key) as T)
    } catch (e: JSONException) {
        Log.w("${ javaClass.simpleName }KEx", e.message)
    }
}

This looks like this jsonObj.getIt<String>("error") { er = it } as compared to jsonObj.getIt<String>("error", JSONObject::getString) { err = it }

UPDATE2: This seems like ultimately a better approach, at least for me and avoids the issues with working with generics to meet the goal

inline fun JSONObject.unless(func: JSONObject.() -> Unit) {
    try {
        this.func()
    } catch (e: JSONException) {
        Log.w("${ javaClass.simpleName }KEx", e.message)
    }
}

Using:

jsonObj.unless {
    sDelay = getLong("s_delay") * 1000
    wDelay = getLong("w_delay") * 1000
}
jsonObj.unless { sam = getBoolean("sam") }
jsonObj.unless { err = getString("error") }

1 Answers1

1

The kotlin is operator takes an object on the left, and a class reference on the right. You can just use a simple equality check there since you already have class references on both sides.

T is still unknown at compile time, so taking this.get*() as T doesn't make a whole lot of sense. You'll have already verified the type in your when block so you might as well use it.

As a matter of completeness you'll probably want to also include an else block in case someone calls jsonObject.getIt<Date>(...).

I've also included a second version that takes an extra parameter to invoke, but sounds like a less verbose version of what you originally wanted. It takes the key, accessor, and block, and works on any existing and new accessors added to JSONObject in the future without reification or modifications to the extension.

inline fun <reified T> JSONObject.getIt(key: String, block: (value: T) -> Unit) {
    try {
        when (T::class) {
            kotlin.Long::class -> block(this.getLong(key) as Long)
            kotlin.String::class -> block(this.getString(key) as String)
            kotlin.Boolean::class -> block(this.getBoolean(key) as Boolean)
        }
    } catch (e: JSONException) {
        Log.w("fetchFromJSONObject", e.message)
    }
}

inline fun <T> JSONObject.getIt(key: String, accessor: JSONObject.(String) -> T, block: (T) -> Unit) {
    try {
        accessor(key).let(block)
    } catch (e: JSONException) {
        Log.w("fetchFromJSONObject", e.message)
    }
}
nickrak
  • 1,635
  • 15
  • 18
  • Thanks for the reply. The top however does not run, @ParameterName T was expected... so changing it to T.Then running this code at least on Android does not work. The `when` lambda is never executed. I put an else in and that is executed. The second version with the accessor does work. – user2267362 Jul 24 '17 at 18:43