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") }
3
  • 4
    Please just post your class to point out your problem. Commented Jul 21, 2017 at 5:16
  • I updated with sample code, hopefully this helps Commented Jul 21, 2017 at 20:03
  • Thanks to @nickrack for answering. The second solution works but requires the accessor, which duplicates indicating the type. The first solution does not work with a when. Commented Jul 24, 2017 at 18:46

1 Answer 1

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)
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.