ansman/kotshi

Allow JsonQualifier with dynamic values

Closed this issue · 5 comments

Kotshi uses Moshi.adapter(Type, Class<? extends Annotation>) to retrieve adapter
If we have a json qualifier like:

@JsonQualifier
@WrappedInObject(val name: String)

the code will crash with IllegalArgumentException("WrappedInObject must not declare methods.")

Given some JSON:

{
    "id":"some-id",
    "name":"John Doe",
    "status": {
        "code":"verified"
    },
    "some_field":{
        "name":"a name"
    }
}

I want to achieve something like

@JsonQualifier
annotation class WrappedInObject(val name: String)

class WrappedInObjectAdapter(private val name: String) : JsonAdapter<String>() {
    override fun fromJson(reader: JsonReader): String? {
        reader.beginObject()
        var value: String? = null
        while (reader.hasNext()) {
            when (reader.nextName()) {
                name -> value = reader.nextString()
                else -> reader.skipValue()
            }
        }
        reader.endObject()
        return value
    }

    override fun toJson(writer: JsonWriter, value: String?) {
        writer.beginObject()
        writer.name(name)
        if (value == null) {
            writer.nullValue()
        } else {
            writer.value(value)
        }
        writer.endObject()
    }

    class Factory : JsonAdapter.Factory {
        override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>? {
            val annotation = annotations.filterIsInstance<WrappedInObject>().firstOrNull()
            if(annotation != null) {
                return WrappedInObjectAdapter(annotation.name)
            }
            return null
        }
    }
}

data class Foo(
        val id: String,
        val name: String,
        @WrappedInObject(name = "code")
        @Json(name = "status")
        val statusCode: String,
        @WrappedInObject(name = "name")
        @Json(name = "some_field")
        val someFieldName: String
)

Kotshi needs to pass annotation received from JsonAdapter.Factory.create method into moshi.adapter

moshi.adapter(Type type, Set<? extends Annotation> annotations)
instead of
moshi.adapter(Type type, Class<? extends Annotation> annotationType)

Maybe it's difficult to achieve with code generation because we need the instance of WrappedInObject when instantiating KotshiFooJsonAdapter but it isn't available yet, please close this issue if it's something Kotshi can't support

Well, we could always use our own implementation but I’m sure there is a reason why Moshi doesn’t allow it to define methods.

Just one thing to try, try to put @JvmField on the property. It’s a long shot but it might work.

This is supported by Moshi. (See my 2 example PRs from last night.) It's easier to look at the annotations reflectively.

I would like try implementing this in Kotshi tonight. I'm not sure how it's going to work, so I'm hoping to learn something new.

I use @JvmField, but I think its not the case
With kotshi I need different qualifiers for different names
Something like this will work with kotshi

@JsonQualifer
@NameWrappedInObject
@JsonQualifer
@CodeWrappedInObject
class Factory : JsonAdapter.Factory {
    override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*> {
        if(annotations.any { it.annotationClass == NameWrappedInObject::class }) {
            val annotation = annotations.filterIsInstance<NameWrappedInObject>().first()
            return WrappedInObjectAdapter("name")
        }
        if(annotations.any { it.annotationClass == CodeWrappedInObject::class }) {
            val annotation = annotations.filterIsInstance<CodeWrappedInObject>().first()
            return WrappedInObjectAdapter("code")
        }
        return null
    }
}