0

I have a lot of code like this, it is all the same except for the type PositionJson, it could be AnotherJson or FooJson or BarJson

Is there some way I can exctract all this code into one function that I can somehow pass into it the type? So that I don't have several of these big blocks of almost identical code littering my class?

I'm not sure if this is possible or not, just thought I'd ask because it would be nice to do...

    /**
     * @return the _open_ [PositionJson]s
     */
    val positions: Array<PositionJson>?
        @Throws(AccountsAPIException::class)
        get() {
            val service = constructServiceURL(POSITIONS, null, true)
            try {
                val messageJson = mapper.readValue<MessageJson<Array<PositionJson>>>(
                        callURL(service),
                        object: TypeReference<MessageJson<Array<PositionJson>>>() {

                        })

                val error = messageJson.error
                if (error != null) throw AccountsAPIException(error.errorCode, error.description)
                return messageJson.data
            } catch (e: Exception) {
                throw AccountsAPIException(e)
            }
        }
ycomp
  • 8,316
  • 19
  • 57
  • 95

1 Answers1

2

You can do what you want with generics. However, to use generics we first need to extract that giant block of code into a method:

val positions: Array<PositionJson>? get() = getPositions()

fun getPositions(): Array<PositionJson>? {
    ...
}

We haven't solved the problem, but now we're in a position to be able to solve it by making getPositions generic (note that I also rename the function):

val positions: Array<PositionJson> get() = getArrayOf<PositionJson>()
// thanks to type inference I can omit the type on getArrayOf if desired:
val positions: Array<PositionJson> get() = getArrayOf()

fun <T> getArrayOf(): Array<T>? {
    val service = constructServiceURL(POSITIONS, null, true)
    try {
        val messageJson = mapper.readValue<MessageJson<Array<T>>>(
                callURL(service),
                object: TypeReference<MessageJson<Array<T>>>() {

                })

        val error = messageJson.error
        if (error != null) throw AccountsAPIException(error.errorCode, error.description)
        return messageJson.data
    } catch (e: Exception) {
        throw AccountsAPIException(e)
    }
}

Perfect! Except this won't compile thanks to type erasure. But we can fix this too by making the function inline and making the type parameter reified:

inline fun <reified T: Any> getArrayOf(): Array<T>? {
    ...
}

And that should do it. Now you can reuse this function as needed:

val positions: Array<PositionJson>? get() = getArrayOf()
val persons: Array<PersonJson>? get() = getArrayOf()
val bananas: Array<BananaJson>? get() = getArrayOf()

inline fun <reified T: Any> getArrayOf(): Array<T>? {
    val service = constructServiceURL(POSITIONS, null, true)
    try {
        val messageJson = mapper.readValue<MessageJson<Array<T>>>(
                callURL(service),
                object: TypeReference<MessageJson<Array<T>>>() {

                })

        val error = messageJson.error
        if (error != null) throw AccountsAPIException(error.errorCode, error.description)
        return messageJson.data
    } catch (e: Exception) {
        throw AccountsAPIException(e)
    }
}

One last thing: note that in all my examples I used property getters (get() = ...) as in your original code. However, I strongly suspect that you do NOT want to use a getter. Getters will be called every time someone accesses your property, which in this case means that every time someone reads the positions property you'll be calling constructServiceURL and making the service call, etc. If you want that code to only happen once then you should just call getArrayOf() once and assign the result to your property:

val positions: Array<PositionJson>? = getArrayOf()
// this syntax would also work:
val positions = getArrayOf<PositionJson>()
ean5533
  • 8,884
  • 3
  • 40
  • 64
  • I also just noticed the `POSITIONS` variable, which OP didn't call out in the original question. That can probably also be extracted out as a parameter to the function call: `getArrayOf(POSITIONS)` – ean5533 Mar 31 '17 at 04:10
  • yes `POSITIONS` is just a url fragment, it can be easily made into a parameter – ycomp Mar 31 '17 at 04:26
  • I've seem to have run into a snag with this line `object: TypeReference>>() {` - it produces a type conversion exception (`java.lang.Object cannot be cast....` ) - if I replace `T` on this line with, e.g. `PositionJson` then it works - what should I do? I mean the other `T`s in the code are ok, only this one is a problem – ycomp Mar 31 '17 at 08:43
  • @ycomp What common class do your `*Json` objects derive from? Alternatively, what class does the compiler complain that it can't cast to? Let's say that the class name is `JsonBase` -- change your generic signature to require that `T` is a subclass of `JsonBase`: `inline fun getArrayOf()` – ean5533 Mar 31 '17 at 13:02
  • that's the thing... they extend only Object/Any (I mean there is nothing specified in the class definition). It complains about casting to Object `java.lang.Object cannot be cast....` - and I guess this doesn't matter but they are Kotlin classes - not java classes – ycomp Mar 31 '17 at 13:59
  • @ycomp You're eliding part of the compiler output. What is the rest of it? – ean5533 Mar 31 '17 at 15:16
  • `java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Laccount.(morepackages).PositionJson;`and it goes on just to indicate that it happens on the line where I query the property `val p=positions` . My code was a bit more complicated than that originally, but I added this line just to verify that it actually is from only calling the getter. `Position Json` is defined as `class PositionJson {...various Long, String?, Double? var fields...}` – ycomp Apr 01 '17 at 00:00
  • [TypeReference (Jackson-core 2.0.0 API)](https://fasterxml.github.io/jackson-core/javadoc/2.0.0/com/fasterxml/jackson/core/type/TypeReference.html) is from the Jackson library – ycomp Apr 01 '17 at 00:08
  • basically the `messageJson.data` contains an array of `Object` (when using ) but array of `PositionJson` if I switch that single `TypeReference` line reference to a ) ... I'm not really good with this more advanced generics stuff, which is why I asked the question - but, my guess is that we want `message.data` to be an array of ? – ycomp Apr 01 '17 at 00:18
  • The exception is thrown on `return messageJSon.data` – ycomp Apr 01 '17 at 00:24
  • i'm thinking the answer might lie somewhere [here](https://stackoverflow.com/questions/34716697/how-do-i-deserialize-json-into-a-listsometype-with-kotlin-jackson) or [there](https://stackoverflow.com/questions/33368328/how-to-use-jackson-to-deserialize-to-kotlin-collections) – ycomp Apr 02 '17 at 04:12
  • @ycomp I don't immediately see what would cause that compile time error and it's difficult for me to recreate it. You might want to ask in a new question to get more visibility. – ean5533 Apr 03 '17 at 15:06
  • Yes i was thinking to do that, just need to figure out how to phrase it.its runtime error, compiles fine.the problem is with Jackson and kotlin I think. – ycomp Apr 03 '17 at 15:11