SalomonBrys / Kotson

Kotlin bindings for JSON manipulation via Gson
MIT License
709 stars 37 forks source link

Either none or all type parameters can be wildcard #11

Closed westito closed 8 years ago

westito commented 8 years ago

I get "Either none or all type parameters can be wildcard in Map<String, Competition2>" when pass this Map class as parameter. This worked in the previous version. Without kotson gson can handle it well.

SalomonBrys commented 8 years ago

I don't really understand the issue. Can you show a chunk of code to make it clearer ?

westito commented 8 years ago

inline fun <reified T: Any> DataSnapshot.toObject(): T? = try { Dep.gson.fromJson<T>(JsonHelpers.getMapper().writeValueAsString(this.value)) } catch (e: Exception) { e.printStackTrace() Crashlytics.logException(e) null }

it.toObject<Map<String, Competition2>>()?.let {...

I used this code and worked perfectly until the last version. I found a solution. Wrapped in a class and now it's working class CompetitionPair : Map<String, Competition2>()

sfunke commented 8 years ago

I have a related issue:

I try to map a JSON list to my custom Object list with Kotson's extensions: Gson().fromJson<List<MyCustomObj>>(json)

Instead of my List<MyCustomObj>, I get a List<LinkedTreeMap> (which is Gsons default). This has been working in 1.7 version, and to me it seems that there may be an issue in GsonBuilder.kt in the typeToken function:

inline fun <reified T: Any> typeToken(): Type {
    val type = object : TypeToken<T>() {} .type

    if (type is ParameterizedType) {
        if (type.actualTypeArguments.any { it is WildcardType }) {
            if (!type.actualTypeArguments.all { it is WildcardType })
                throw IllegalArgumentException("Either none or all type parameters can be wildcard in $type")
            return type.rawType
        }
    }
    return type
}

In particular, I think the return type.rawType does not make sense, since it now returns the rawtype (List) as soon as at least one single argument is a WildcardType. I think this may be a typo?

If it's not a typo and wanted behaviour, could you shed some light on why you introduced that check, and how one could work around it? I think part of my problem is, that Kotlin's list classes always resolve to bounded parameterized Lists List<? extends MyType>...

SalomonBrys commented 8 years ago

Yes, this is an unwanted behaviour, for which I apologize. I think that it is indeed because Kotlin's List interface has declaration site variance. This will be corrected first thing in the morning (Paris time).

sfunke commented 8 years ago

Thanks, no need to apologize, bugs happen :-) I appreciate your efforts, great library.

SalomonBrys commented 8 years ago

OK, I've just pushed 2.0.1 in Maven Central that corrects this bug.

Now for the _why_:

Let's say I have a class Command<T>. Using Kotson, I want to be able to register a type adapter on Command<*>. Meaning that I want to be able to register a serializer / deserializer on any Command. That was not really possible in v1.7 because the typeToken function would return the ParameterizedType Command<?>, and not class Command. So later when Gson would saw that it has to deserialize, say, a Command<Person>, it would not match. The only way was to use registerTypeHierarchyAdapter instead of registerTypeAdapter because it uses a Class and not a Type, which was not really semantic.

So the idea was to detect in the typeToken function if a generic Command<*> was used, in which case register the Class so that any Command would be deserialized accordingly. If a specialized Command<Whatever> was used, then it would return the classic ParameterizedType.

That's the reason of the "Either none or all type parameters can be wildcard". Really, Gson can't handle a semi-generalized type adapter such as Map<String, *>.

Now, of course, I forgot to take into account that, in java, List<?> is really List<? extends Object>. Meaning that both List<?> and List<? extends Person> are both ParameterizedType with a WildCardType argument. You know the rest.

Now, I've corrected my mistake by having typeToken revert to it's old behaviour, and having registerTypeAdapter use a registrationTypeToken function. This new registrationTypeToken function acts as explained, returning the Class and not ParameterizedType when a wildcard is used, only now it also checks that the wildcard has Object in it's upper bound, in order to differentiate List<*> and List<out Whatever>.

I'm not an expert in Java reflexivity so please do let me know if you think this is not the best course of action ;)

SalomonBrys commented 8 years ago

TLDR; Koston 2.0.1 is in Maven Central. It should correct the issue. Please let me know if it doesn't. Thanks for the report, and sorry for the inconvenience ;)

sfunke commented 8 years ago

Thank you, I don't know if I get to test it today or tomorrow, but definitely in the next days. Thank you for the quick fix!

sfunke commented 8 years ago

Ok, I can confirm the bug is fixed :) Thanks for your efforts!