-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
properties with list of values #152
properties with list of values #152
Conversation
Hey, I'll take a look |
Thanks @Chuckame, much appreciated! |
@potfur, thank you very much for the PR and sorry for the delay. Wouldn't it be enough to just change @SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
@Repeatable
annotation class AvroProp(val key: String, val value: String)
//Example usage
@Serializable
@AvroProp("cold", "play")
@AvroProp("some", "thing")
data class TypeAnnotated(val str: String) Another option which does not require kotlin 1.6 is to define the new annotation @SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
annotation class AvroProps(vararg val props: AvroProp)
//Example usage
@Serializable
@AvroProps(
AvroProp("cold", "play"),
AvroProp("some", "thing")
)
data class TypeAnnotated(val str: String) I'm in favor of the first solution as this is a classic example of why repeatable annotations have been introduced. But I would like to hear your opinion on it. |
The problem that I'm trying to solve is to be able to define
From what I know, repeating annotation will result in something like:
So, it's about being able to have list of values for key instead multiple keys with single value. |
With the comment of @thake it would result to something like: @AvroProp("cold", "play")
@AvroProp("cold", "some")
@AvroProp("cold", "thing")
data class TypeAnnotated(val str: String) Personally I don't really like it as it feels really verbose. |
Or, we could take into the middle: Just modify |
@potfur, thanks for fixing my confusion ;) I'm totally with @Chuckame, the breaking change seems to be legit. @potfur can you also add the repeatable annotation? @SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
@Repeatable
annotation class AvroProp(val key: String, vararg val values: String) |
If breaking change is way to go - even better. |
But an near invisible breaking change since it's not changing the type but only the name (I think it's not common to call this annotation with class' property names 🤞 ) so users will continue to call like this But let's make a major increase when releasing this PR. |
Oh we missed something important @thake , that prevent us to have like a common So new idea, we keep annotation class AvroListProp(val key: String, values: Array<String>) |
src/main/kotlin/com/github/avrokotlin/avro4k/AnnotationExtractor.kt
Outdated
Show resolved
Hide resolved
Something like this (below) will work nicely, but has it's own limitations. @SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
annotation class AvroProp(val key: String, val value: String = "", val values: Array<String> = []) Mainly non-blank So maybe, let's go back to my initial proposal with minor improvements. @SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
annotation class AvroProp(val key: String, val value: String)
@SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
annotation class AvroProps(val key: String, val values: Array<String>) As for |
Let's go to your initial proposal yes. Just the name, I would prefer |
I'll have a look into it tomorrow. |
@thake it has been changed to |
@thake, @potfur thanks for your patience. We have a similar situation with the Proposal: @SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
@Repeatable
annotation class AvroJsonProp(
val key: String,
val jsonValue: String)
//Usage
data class TypeAnnotated(
@AvroJsonProp("array", """["play","blub"]""")
@AvroJsonProp("obj", """{"javascript":"object"}""")
val str: String
) |
@thake I'm not really a fan of this, since json in strings are source of errors and ambiguous, and regarding performances it requires parsing the value each time with ser/deser. We will have to make a cache etc... Why not just having a supplier class, to have a typesafe implementation ? See the following : annotation class AvroGenericProp(
val key: String,
val valueSupplier: KClass<out AvroPropSupplier<*>>,
)
interface AvroPropSupplier<T> {
val serializer: SerializationStrategy<T>
fun getValue(): T
}
//usage
data class TypeAnnotated(
@AvroGenericProp("array", CustomPropSupplier::class)
val str: String
)
@Serializable
data class CustomObject(val field1: String)
object CustomPropSupplier : AvroPropSupplier<CustomObject> {
override val serializer = CustomObject.serializer()
override fun getValue() = CustomObject("value")
} That way we are sure that we can put whatever we want as inside the field value, it can be static or we can pass some context to the getValue (like the annotated field's value, the parent class, etc...). And it's not so complex to implement, what do you think ? |
I followed @thake suggestion, as it is more as Avro actually suggests with handling this case, and it also brings bit more flexibility. About |
I don't understand, avro only suggest to handle all the types in additional props. Why we continue using json while we have pure kotlin to write objects ? 🤔 |
@@ -8,6 +8,10 @@ import kotlinx.serialization.SerialInfo | |||
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) | |||
annotation class AvroProp(val key: String, val value: String) | |||
|
|||
@SerialInfo | |||
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) | |||
annotation class AvroJsonProp(val key: String, val jsonValue: String) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could use @Language("json")
coming from jetbrains to validate json at least in jetbrains editors, what do you think ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would improve the development experience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just added it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add it also to the AvroDefault
annotation ? 👼
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@Chuckame I get your point, and for sure it would be better to use proper types. On the other hand, the IMHO, doing improvements on types, and adding |
I have exactly the same concern about Let's do AvroJsonProp, but it won't be that performant. I'll create an issue to discuss about "type-safe" AvroDefault, AvroJsonProp and other jsons annotations. |
Given the backward-compatible nature of these changes, could we get a new release out once @thake has approved this PR? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some small remarks. After merging this, we'll release a new version.
@@ -26,6 +26,7 @@ class AnnotationExtractor(private val annotations: List<Annotation>) { | |||
annotations.filterIsInstance<AvroAliases>().flatMap { it.value.toList() } | |||
} | |||
fun props(): List<Pair<String, String>> = annotations.filterIsInstance<AvroProp>().map { it.key to it.value } | |||
fun json(): List<Pair<String, String>> = annotations.filterIsInstance<AvroJsonProp>().map { it.key to it.jsonValue } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fun json(): List<Pair<String, String>> = annotations.filterIsInstance<AvroJsonProp>().map { it.key to it.jsonValue } | |
fun jsonProps(): List<Pair<String, String>> = annotations.filterIsInstance<AvroJsonProp>().map { it.key to it.jsonValue } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Function renamed
import io.kotest.matchers.shouldBe | ||
import kotlinx.serialization.Serializable | ||
|
||
class AvroJsonPropSchemaTest : WordSpec() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add some tests for object properties? Just to make sure that this is also correctly processed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean adding tests for a JSON value that is an object with properties?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ask because there's already the @AvroJsonProp should support props annotation on field
test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean adding tests for a JSON value that is an object with properties?
Exactly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I simply extended that test by annotating a property using a JSON object with heterogeneous property types.
It is a kind of trade-off between "ease of use" in combination with pre-existing schemas and type safety. Using JSON in the annotations makes it easy to model your kotlin code so that it matches an existing schema; you just copy and paste the value from the asvc schema file. Performance-wise, it shouldn't impact serialization and deserialization. The annotations' JSON is only interpreted whenever a schema is derived from the kotlin class. The schema is usually only derived once for every serialized class (responsibility of the user) and not on every serialization/deserialization. |
@thake Since we'll have the For the performances, If we use the |
FYI, I'm playing with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thank you very much for your work!
@Chuckame / @thake Alas There are a number of open issues such as KTIJ-16874 and IDEA-173477 related to the processing of |
@franckrasolo as I can read, both are related not to our case: we are directly using this language annotation on the value parameter. Those issues are related to meta annotations, implying that annotations (like Also tested |
Yes, not an exact match, but the number of issues makes me wonder how stable the processing of |
Avro AVSC allows for custom annotations as shown here.
Same functionality can be partially achieved by using
@AvroProp
annotation, but only for single value annotations.For cases - like the linked issue, where a list of values is needed, existing annotation is not enough.
Therefore I'm suggesting adding
@AvropProps("name", "valueA", "valueB", "etc")