I'm building a schema generator and I'm trying to get the JsonProperty of enum values, for example, for this class:
enum class Size {
#JsonProperty("really-tall") TALL,
#JsonProperty("really-grande") GRANDE;
}
I'd like to get the list "really-tall", "really-grande".
How do I access the annotation of an enum?
Thanks!
UPDATE:
Solution based on this reply for a generic KType:
return (jvmErasure.java as Class<Enum<*>>)
.enumConstants
.map {
it.javaClass.getField(it.name).getAnnotation(JsonProperty::class.java)?.value // Get the JsonProperty string first if exists
?: it.name
}
Update: Additional question from OP
How do I make the first approach work for a generic KType
inline fun <reified T : Enum<T>> getJsonPropertyAnnotations() = enumValues<T>().map {
it.declaringClass
.getField(it.name)
.getAnnotation(JsonProperty::class.java)
.value
}
class SomeTest : StringSpec({
"getJsonPropertyAnnotations" {
getJsonPropertyAnnotations<Size>()
shouldBe listOf("really-tall", "really-grande")
}
})
Please note that with Kotlin 1.7, IntelliJ may show a deprecation warning with wrong replacement for declaringClass in getJsonPropertyAnnotations. I guess this will be sorted out in later versions. Link to related source
The following code should do what you want.
class SomeTest : StringSpec({
"getting annotation values" {
val result = enumValues<Size>().map {
it.declaringClass.getField(it.name).getAnnotation(JsonProperty::class.java).value
}
result shouldBe listOf("really-tall", "really-grande")
}
})
An alternative (less code): Add a String property to your enum class (I called it someFieldName in the below code), annotate with #get:JsonValue, and construct each enum entry with the string value you want. #get:JsonValue will use someFieldName instead of the enum value during serialization.
enum class Size(#get:JsonValue val someFieldName: String) {
TALL("really-tall"),
GRANDE("really-grande");
}
Same test again
class SomeTest : StringSpec({
"getting prop values" {
val result = enumValues<Size>().map {
it.someFieldName
}
result shouldBe listOf("really-tall", "really-grande")
}
})
We're using the latter approach in an ongoing project.
Here's a Kotlin implementation of the technique suggested by user #aSemy.
It's an extension function on ObjectMapper for asking the mapper how it would serialize the values of an enum. This is more robust that just inspecting the #JsonProperty annotation, since it works with #JsonValue as well as any custom annotation introspectors registered with the mapper.
inline fun <reified T : Enum<T>> ObjectMapper.enumValues() : List<String> {
return convertValue(
kotlin.enumValues<T>(),
jacksonTypeRef<List<String>>()
)
}
Usage:
println(jsonMapper().enumValues<Size>())
Related
Is it possible to use Spring AOP (AspectJ) with Kotlin properties? Specifically due to how Kotlin compiles properties to Java:
a getter method, with the name calculated by prepending the get prefix
a setter method, with the name calculated by prepending the set prefix (only for var properties)
a private field, with the same name as the property name (only for properties with backing fields)
Consider the following minimal reproducible example:
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION)
annotation class TestAnnotation
...
#Aspect
class TestAspect {
#Around("#annotation(annotation)")
fun throwingAround(joinPoint: ProceedingJoinPoint, annotation: TestAnnotation): Any? {
throw RuntimeException()
}
}
...
internal class MinimalReproducibleExample {
open class TestProperties {
#TestAnnotation
val sampleProperty: String = "sample property"
#TestAnnotation
fun sampleFunction(): String = "sample function"
}
private lateinit var testProperties: TestProperties
#BeforeEach
fun setUp() {
val aspectJProxyFactory = AspectJProxyFactory(TestProperties())
aspectJProxyFactory.addAspect(TestAspect())
val aopProxyFactory = DefaultAopProxyFactory()
val aopProxy = aopProxyFactory.createAopProxy(aspectJProxyFactory)
testProperties = aopProxy.proxy as TestProperties
}
#Test
fun test() {
println(testProperties.sampleProperty)
println(testProperties.sampleFunction())
}
}
Running the test yields:
null
sample function
When debugging I can see that the generated proxy is a cglib-backed proxy, which should be able to proxy to a concrete class, but it does not seem to invoke the configured aspect. Is there something wrong with my #Around definition, or is this a limitation of Kotlin properties and/or proxying concrete classes?
Was able to trigger the aspect above with the following changes:
Use a "site target" for the getter: #get:TestAnnotation
Make the property/function both open
I have Sealed Class like below.
sealed class Number {
object One: Number() {
}
object Two: Number() {
}
object Three: Number() {
}
object Four: Number() {
}
}
then, I want to use One, and Two using sealedSubclasses.
// this code compile error. `Operator '==' cannot be applied to 'KClass<out KClass<out Number>>' and 'Number.One'`
val subClasses = Number::class.sealedSubclasses.filter { clazz -> clazz::class == Number.One }
Can I solve this? Do you know any Idea?
Use clazz == Number.One::class.
Currently, you’re using clazz::class which is KClass<out KClass<out Number>>, the class of what was already a class.
And you forgot the ::class at the end.
However, you’re filtering to find out which subclass is the class you already know so there’s no point. You could just replace you’re whole line of code with val subclasses = listOf(Number.One::class).
I want to make Jackson work with enums not by name and not by ordinal, but with a custom property I added called "stringId".
I wanted to support this with all Enums in the system so I made an interface called StringIdEnum which the FooEnum will implement.
I'm using Kotlin so I created a property in the interface called stringId which I override in each enum value.
Now I want to make Jackson serialize and deserialize using this stringId field, from what I seen I have several options:
Use #JsonProperty annotation on each enum value and make sure it is aligned with the stringId property.
I see two issues with this approach. one it's a lot of annotation to add (we have many enum classes across the system). two I need to make sure the annotation value and the property value should be always the same which can cause issues in the future.
I tried to use the READ_ENUMS_USING_TO_STRING feature, but because I'm using an interface I can't override the toString in the interface class (I can override it in every enum class but that again seems like a lot of redundant code)
Implement a custom serializer/deserializer.
The serializer is pretty straightforward, however, I had trouble with the deserializer.
I wanted to register the deserializer on the StringIdEnum interface, but I had an issue getting all the runtime enum values for the actual FooType enum.
StringIdEnum:
interface StringIdEnum {
val stringId: String
}
enum class FooType(override val stringId: String) : StringIdEnum {
FOO("FOO"),
GOO("GOO");
}
Managed to get it working:
#JsonSerialize(using = StringIdEnumSerializer::class)
#JsonDeserialize(using = StringIdEnumDeserializer::class)
interface StringIdEnum: DbEnum {
val stringId: String
}
class StringIdEnumSerializer: StdSerializer<StringIdEnum>(StringIdEnum::class.java) {
override fun serialize(value: StringIdEnum, gen: JsonGenerator, provider: SerializerProvider) {
gen.writeString(value.stringId)
}
}
class StringIdEnumDeserializer : JsonDeserializer<Enum<*>>(), ContextualDeserializer {
private lateinit var type: JavaType
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Enum<*> {
val t = p.text
val enumConstants = (type.rawClass as Class<Enum<*>>).enumConstants
return enumConstants.single { (it as StringIdEnum).stringId == t }
}
override fun createContextual(ctxt: DeserializationContext?, property: BeanProperty?): JsonDeserializer<*> {
val wrapperType: JavaType = property!!.type
val stringIdEnumDeserializer = StringIdEnumDeserializer()
stringIdEnumDeserializer.type = wrapperType
return stringIdEnumDeserializer
}
}
If I have a following interface:
interface BaseDataRemote<T, in Params> {
fun getData(params: Params? = null): Single<T>
}
Would it be possible have implementation of this interface that does not take Params?
To have effectively something like:
interface BaseDataRemote<T> {
fun getData(): Single<T>
}
Implementation is as follows:
class RemoteSellerDataSource #Inject constructor(
private val sellerApi: SellerApi,
#Named("LANG") private val lang: String
) : BaseDataRemote<SellerEntity, Nothing> {
override fun getData(params: Nothing?): Single<SellerEntity> {
return sellerApi.getSeller(lang).map { it.fromApiEntity() }
}
}
I use Dagger 2 to module to bind this implementation:
#Module
internal interface RemoteModule {
#Binds
#CoreScope
fun bindsSellerRemote(remoteSellerDataSource: RemoteSellerDataSource): BaseDataRemote<SellerEntity, Nothing>
}
I tried using Nothing as second type parameter, but it does not seem to work
(I'm getting required: class or interface without bounds error
Full error message:
RemoteSellerDataSource.java:6: error: unexpected type
public final class RemoteSellerDataSource implements com.bigchangedev.stamps.business.sdk.data.base.data.BaseDataRemote<SellerEntity, ?> {
^
required: class or interface without bounds
found:?
Thanks.
EDIT: the original answer was a pure Kotlin answer because the OP didn't mention Dagger.
Using Nothing is correct and works in pure Kotlin. However, Dagger seems to convert your code to Java, and in doing so it uses wildcards for the generics (which it doesn't like because it wants exact type matches). To avoid this issue, you can try using #JvmSuppressWildcards on your generic type parameters:
class RemoteSellerDataSource #Inject constructor(
private val sellerApi: SellerApi,
#Named("LANG") private val lang: String
) : BaseDataRemote<SellerEntity, #JvmSuppressWildcards Nothing> {
override fun getData(params: Nothing?): Single<SellerEntity> {
return sellerApi.getSeller(lang).map { it.fromApiEntity() }
}
}
Although I'm not sure what will happen in Java with Nothing in that case. I guess this should have the same effect on the Java code as removing the in variance for the second type param in the interface declaration, but without weakening your Kotlin types.
Another workaround would be to use Unit instead of Nothing, which Dagger will most likely convert to Void in this case. This is not great for your types, though.
Original answer:
You can technically already call getData() without arguments thanks to the default value. An implementation that doesn't care about the params argument can simply expect null all the time.
The Kotlin type that only contains null and no other value is technically Nothing?, and since getData is defined with Params? (note the ?) as input, it should be correct to specify Nothing (even without ?) as second type argument. So you should be able to define an implementation like this:
interface BaseDataRemote<T, in Params> {
fun getData(params: Params? = null): Single<T>
}
class ImplementationWithoutParams<T> : BaseDataRemote<T, Nothing> {
override fun getData(params: Nothing?): Single<T> {
// params will always be null here
}
}
To avoid confusion for the users, this implementation may additionally provide a getData() method without arguments at all:
class ImplementationWithoutParams<T> : BaseDataRemote<T, Nothing> {
override fun getData(params: Nothing?): Single<T> = getData()
fun getData(): Single<T> {
TODO("implementation")
}
}
I have a framework written in Java that, using reflection, get the fields on an annotation and make some decisions based on them. At some point I am also able to create an ad-hoc instance of the annotation and set the fields myself. This part looks something like this:
public #interface ThirdPartyAnnotation{
String foo();
}
class MyApp{
ThirdPartyAnnotation getInstanceOfAnnotation(final String foo)
{
ThirdPartyAnnotation annotation = new ThirdPartyAnnotation()
{
#Override
public String foo()
{
return foo;
}
};
return annotation;
}
}
Now I am trying to do the exact thing in Kotlin. Bear in mind that the annotation is in a third party jar.
Anyway, here is how I tried it in Kotlin:
class MyApp{
fun getAnnotationInstance(fooString:String):ThirdPartyAnnotation{
return ThirdPartyAnnotation(){
override fun foo=fooString
}
}
But the compiler complains about: Annotation class cannot be instantiated
So the question is: how should I do this in Kotlin?
You can do this with Kotlin reflection:
val annotation = ThirdPartyAnnotation::class.constructors.first().call("fooValue")
In the case of annotation having no-arg constructor (e.g. each annotation field has a default value), you can use following approach:
annotation class SomeAnnotation(
val someField: Boolean = false,
)
val annotation = SomeAnnotation::class.createInstance()
This is the solution I might have found but feels like a hack to me and I would prefer to be able to solve it within the language.
Anyway, for what is worth,it goes like this:
class MyApp {
fun getInstanceOfAnnotation(foo: String): ThirdPartyAnnotation {
val annotationListener = object : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
return when (method?.name) {
"foo" -> foo
else -> FindBy::class.java
}
}
}
return Proxy.newProxyInstance(ThirdPartyAnnotation::class.java.classLoader, arrayOf(ThirdPartyAnnotation::class.java), annotationListener) as ThirdPartyAnnotation
}
}