jsonschema-generator icon indicating copy to clipboard operation
jsonschema-generator copied to clipboard

Support Kotlin annotations

Open moranlefler opened this issue 1 year ago • 9 comments

It seems that the Jackson module ignores Jackson annotations in my Kotlin code, e.g.:

 data class TestNamedProperty( @JsonProperty("my_text")  val text: String)

The generator generates a schema with the property name text instead of my_text. I traced the generation with a debugger, and it seems that the method getPropertyNameOverrideBasedOnJsonPropertyAnnotation in the Jackson module does not see the annotation in the Java class that I created from Kotlin using TestNamedProperty::class.java. I suspect that it has to do with how Kotlin and Java annotations differ - https://stackoverflow.com/questions/72559287/how-to-read-kotlin-annotation

I guess that means that jsonschema-generator cannot be used from Kotlin, which is a bummer for my use case :(

moranlefler avatar Jun 05 '23 19:06 moranlefler

It looks like I jumped to a conclusion too quickly as this doesn't seem to be a general problem with Kotlin annotations, as I was able to successfully create a correct schema for a Kotline class like so:

class TestNamedProperty2 {
    @JsonProperty("my_text")
    val text: String = ""
  }

So it seems that the issue is specific to Kotlin data classes. I am writing a schema generator that uses jsonschema-generator and adds a bit of logic and configurations. Many of its (internal) users use data class so that is a hard requirement. Is this something that I can help fix? any pointers for how to approach this?

moranlefler avatar Jun 05 '23 19:06 moranlefler

Another relevant reference: https://stackoverflow.com/questions/64118808/java-annotation-cannot-be-found-per-reflection-on-a-kotlin-data-class

moranlefler avatar Jun 05 '23 19:06 moranlefler

Hi @moranlefler,

It's very unfortunate that the annotation goes to the constructor parameter in this scenario. Currently, those are not taken into account, but they apparently would have to in order to handle data classes correctly.

As starting point, you can extend the JacksonModule and override its getPropertyNameOverrideBasedOnJsonPropertyAnnotation() method (that's why it has protected visibility 😉) to check the constructor parameters as well. https://github.com/victools/jsonschema-generator/blob/5965fddcd0877a4a07118844dd258d8a12f69dcd/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java#L194

Preferably, the handling would be generic enough to allow being included via a PR. Looking forward to your feedback there. 😃

CarstenWickner avatar Jun 05 '23 20:06 CarstenWickner

Created a PR for this. Feedback welcomed! https://github.com/victools/jsonschema-generator/pull/359

moranlefler avatar Jun 08 '23 06:06 moranlefler

@moranlefler Have you tried adding @get to the annotation? Like this:

class TestNamedProperty2 {
    @get:JsonProperty("my_text")
    val text: String = ""
  }

I stumbled upon the same issue using swagger 2 annotations and that did the job.

bfreuden avatar Sep 15 '23 15:09 bfreuden

If this is indeed setting the annotations on the getter methods instead of the constructor, that'd be awesome as no change would be required here.

I'd merely include this in the documentation then, to share this with other Kotlin users. Relevant Kotlin documentation would be this then: https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets

Thanks for sharing!

CarstenWickner avatar Sep 16 '23 13:09 CarstenWickner

This is indeed setting the annotations on getter methods as shown by the output of javap.

private data class MyParams(
    @get:Schema(title="Jinja input", description = "Whole document if left empty, otherwise 'metadata.input' syntax accepted", defaultValue = " ")
    val input: String = "",
)
javap -v -classpath build/classes/kotlin/test org.bfreuden.MyParams

  public final java.lang.String getInput();
    descriptor: ()Ljava/lang/String;
    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #29                 // Field input:Ljava/lang/String;
         4: areturn
      LineNumberTable:
        line 26: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg.bfreuden/MyParams;
    RuntimeVisibleAnnotations:
      0: #43(#44=s#51,#48=s#52,#53=s#54)
        io.swagger.v3.oas.annotations.media.Schema(
          title="Jinja input"
          description="Whole document if left empty, otherwise \'metadata.input\' syntax accepted"
          defaultValue=" "
        )
    RuntimeInvisibleAnnotations:
      0: #11()
        org.jetbrains.annotations.NotNull

 

bfreuden avatar Sep 18 '23 06:09 bfreuden

I too would love to see this available in kotlin.

What I have found is by adding a @get:JsonProperty or @field:JsonProperty to you data class properties fixes the issue, however what about the kotlin classes that are out of your control.

This also has a general problem with kotlin data classes, as the @Min from jakarta.validation-api also cannot work effectively. @get:Min works and hence the problem is a kotlin issue, not that kotlin is going to fix it.

Possible work around... Suggest a new module specifically for kotlin data classes. Might consider using the kotlin reflection instead of java reflection (eg. <Type>::class instead of <Type>::class.java) to gather the information.

SquirrelGrip avatar Dec 07 '23 06:12 SquirrelGrip

however what about the kotlin classes that are out of your control.

@SquirrelGrip I'm not sure I'm getting your point: if a Java class is out of your control then you cannot add annotations (min, JsonProperty) to it in order to tweak the behavior of the json schema generation, right? So why would it be required for Kotlin?

In my understanding there is no issue with Kotlin and no issue with jsonschema-generator (and this ticket should be closed :)).

bfreuden avatar Jan 23 '24 17:01 bfreuden