Skip to content

Commit 88d8b56

Browse files
Remove use of AliasFor and use mappers for Serde annotations (#1012)
using @AliasFor adds annotations as stereotypes which causes problems right now when you have multiple annotations as the values from the stereotypes are not resolved. This resolves that by using mappers instead - **Reproduce the bug with `@Serdeable.` and `@JsonProperty`** - **remove the use of @AliasFor and use mappers instead** --------- Co-authored-by: Denis Stepanov <[email protected]>
1 parent b4d6c7c commit 88d8b56

File tree

6 files changed

+322
-12
lines changed

6 files changed

+322
-12
lines changed

serde-api/src/main/java/io/micronaut/serde/annotation/Serdeable.java

-11
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package io.micronaut.serde.annotation;
1717

18-
import io.micronaut.context.annotation.AliasFor;
1918
import io.micronaut.core.annotation.Introspected;
2019
import io.micronaut.serde.Deserializer;
2120
import io.micronaut.serde.Serializer;
@@ -48,13 +47,11 @@
4847
/**
4948
* @return Whether build time validation should fail compilation on definition errors.
5049
*/
51-
@AliasFor(annotation = SerdeConfig.class, member = SerdeConfig.VALIDATE)
5250
boolean validate() default true;
5351

5452
/**
5553
* @return Naming strategy to use for both serialization and deserialization.
5654
*/
57-
@AliasFor(annotation = SerdeConfig.class, member = SerdeConfig.NAMING)
5855
Class<? extends PropertyNamingStrategy> naming() default IdentityStrategy.class;
5956

6057
/**
@@ -67,26 +64,22 @@
6764
/**
6865
* @return The {@link io.micronaut.serde.Serializer} to use.
6966
*/
70-
@AliasFor(annotation = SerdeConfig.class, member = SerdeConfig.SERIALIZER_CLASS)
7167
Class<? extends Serializer> using() default Serializer.class;
7268

7369
/**
7470
* @return Whether build time validation should fail compilation on definition errors.
7571
*/
76-
@AliasFor(annotation = SerdeConfig.class, member = SerdeConfig.VALIDATE)
7772
boolean validate() default true;
7873

7974
/**
8075
* Use the given class to serialize this type.
8176
* @return A type that is a subclass of the annotated type.
8277
*/
83-
@AliasFor(annotation = SerdeConfig.class, member = SerdeConfig.SERIALIZE_AS)
8478
Class<?> as() default void.class;
8579

8680
/**
8781
* @return Naming strategy to use.
8882
*/
89-
@AliasFor(annotation = SerdeConfig.class, member = SerdeConfig.NAMING)
9083
Class<? extends PropertyNamingStrategy> naming() default IdentityStrategy.class;
9184
}
9285

@@ -100,26 +93,22 @@
10093
/**
10194
* @return The deserializer.
10295
*/
103-
@AliasFor(annotation = SerdeConfig.class, member = SerdeConfig.DESERIALIZER_CLASS)
10496
Class<? extends Deserializer> using() default Deserializer.class;
10597

10698
/**
10799
* @return Whether build time validation should fail compilation on definition errors.
108100
*/
109-
@AliasFor(annotation = SerdeConfig.class, member = SerdeConfig.VALIDATE)
110101
boolean validate() default true;
111102

112103
/**
113104
* Use the given class to deserialize this type.
114105
* @return A type that is a subclass of the annotated type.
115106
*/
116-
@AliasFor(annotation = SerdeConfig.class, member = SerdeConfig.DESERIALIZE_AS)
117107
Class<?> as() default void.class;
118108

119109
/**
120110
* @return Naming strategy to use.
121111
*/
122-
@AliasFor(annotation = SerdeConfig.class, member = SerdeConfig.NAMING)
123112
Class<? extends PropertyNamingStrategy> naming() default IdentityStrategy.class;
124113
}
125114
}

serde-jackson/src/test/groovy/io/micronaut/serde/jackson/SerdeImportSpec.groovy

+156-1
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,7 @@ package custom;
676676
677677
import com.fasterxml.jackson.annotation.JsonCreator;
678678
import com.fasterxml.jackson.annotation.JsonProperty;
679+
import io.micronaut.context.annotation.Bean;
679680
import io.micronaut.core.annotation.NonNull;
680681
import io.micronaut.core.type.Argument;
681682
import io.micronaut.serde.annotation.Serdeable;
@@ -697,7 +698,66 @@ record CustomValue(
697698
}
698699
}
699700
700-
@Singleton
701+
@Bean(typed = CustomSerde.class)
702+
class CustomSerde implements Deserializer<Integer> {
703+
@Override
704+
public Integer getDefaultValue(DecoderContext ignoredContext, Argument<? super Integer> ignoredType) {
705+
return -2;
706+
}
707+
708+
@Override
709+
public Integer deserialize(Decoder decoder, DecoderContext context, Argument<? super Integer> type) throws IOException {
710+
return decoder.decodeInt();
711+
}
712+
713+
@Override
714+
public Integer deserializeNullable(Decoder decoder, DecoderContext context, Argument<? super Integer> type) throws IOException {
715+
if (decoder.decodeNull()) {
716+
return -1;
717+
}
718+
return decoder.decodeInt();
719+
}
720+
}
721+
''')
722+
723+
expect:
724+
jsonMapper.readValue('{"value":1}', typeUnderTest).value() == 1
725+
jsonMapper.readValue('{"value":null}', typeUnderTest).value() == -1
726+
jsonMapper.readValue('{}', typeUnderTest).value() == -2
727+
728+
cleanup:
729+
context.close()
730+
}
731+
void "test custom deserializer 2"() {
732+
733+
given:
734+
def context = buildContext('custom.CustomValue','''
735+
package custom;
736+
737+
import com.fasterxml.jackson.annotation.JsonCreator;
738+
import com.fasterxml.jackson.annotation.JsonProperty;
739+
import io.micronaut.context.annotation.Bean;
740+
import io.micronaut.core.annotation.NonNull;
741+
import io.micronaut.core.type.Argument;
742+
import io.micronaut.serde.annotation.Serdeable;
743+
import io.micronaut.serde.annotation.SerdeImport;
744+
import io.micronaut.serde.Decoder;
745+
import io.micronaut.serde.Deserializer;
746+
import jakarta.inject.Singleton;
747+
import java.io.IOException;
748+
749+
@Serdeable
750+
record CustomValue(
751+
@Serdeable.Deserializable(using = CustomSerde.class)
752+
@NonNull
753+
Integer value
754+
) {
755+
@JsonCreator
756+
public CustomValue {
757+
}
758+
}
759+
760+
@Bean(typed = CustomSerde.class)
701761
class CustomSerde implements Deserializer<Integer> {
702762
@Override
703763
public Integer getDefaultValue(DecoderContext ignoredContext, Argument<? super Integer> ignoredType) {
@@ -727,4 +787,99 @@ class CustomSerde implements Deserializer<Integer> {
727787
cleanup:
728788
context.close()
729789
}
790+
791+
void "test custom serializer"() {
792+
given:
793+
def context = buildContext('custom.CustomValue','''
794+
package custom;
795+
796+
import com.fasterxml.jackson.annotation.JsonCreator;
797+
import com.fasterxml.jackson.annotation.JsonProperty;
798+
import io.micronaut.context.annotation.Bean;
799+
import io.micronaut.core.annotation.NonNull;
800+
import io.micronaut.core.type.Argument;
801+
import io.micronaut.serde.Encoder;
802+
import io.micronaut.serde.Serializer;
803+
import io.micronaut.serde.annotation.Serdeable;
804+
import io.micronaut.serde.annotation.SerdeImport;
805+
import io.micronaut.serde.Decoder;
806+
import jakarta.inject.Singleton;
807+
import java.io.IOException;
808+
809+
@Serdeable
810+
record CustomValue(
811+
@Serdeable.Serializable(using = CustomSerde.class)
812+
@JsonProperty("value")
813+
@NonNull
814+
Integer value
815+
) {
816+
@JsonCreator
817+
public CustomValue {
818+
}
819+
}
820+
821+
@Bean(typed = CustomSerde.class)
822+
class CustomSerde implements Serializer<Integer> {
823+
@Override
824+
public void serialize(Encoder encoder, EncoderContext context, Argument<? extends Integer> type, Integer value) throws IOException {
825+
encoder.encodeInt(123);
826+
}
827+
}
828+
''')
829+
830+
expect:
831+
jsonMapper.writeValueAsString(typeUnderTest.type.newInstance(1)) == """{"value":123}"""
832+
jsonMapper.writeValueAsString(typeUnderTest.type.newInstance(2)) == """{"value":123}"""
833+
jsonMapper.writeValueAsString(typeUnderTest.type.newInstance(3)) == """{"value":123}"""
834+
835+
cleanup:
836+
context.close()
837+
}
838+
839+
void "test custom serializer 2"() {
840+
given:
841+
def context = buildContext('custom.CustomValue','''
842+
package custom;
843+
844+
import com.fasterxml.jackson.annotation.JsonCreator;
845+
import com.fasterxml.jackson.annotation.JsonProperty;
846+
import io.micronaut.context.annotation.Bean;
847+
import io.micronaut.core.annotation.NonNull;
848+
import io.micronaut.core.type.Argument;
849+
import io.micronaut.serde.Encoder;
850+
import io.micronaut.serde.Serializer;
851+
import io.micronaut.serde.annotation.Serdeable;
852+
import io.micronaut.serde.annotation.SerdeImport;
853+
import io.micronaut.serde.Decoder;
854+
import jakarta.inject.Singleton;
855+
import java.io.IOException;
856+
857+
@Serdeable
858+
record CustomValue(
859+
@Serdeable.Serializable(using = CustomSerde.class)
860+
@NonNull
861+
Integer value
862+
) {
863+
@JsonCreator
864+
public CustomValue {
865+
}
866+
}
867+
868+
@Bean(typed = CustomSerde.class)
869+
class CustomSerde implements Serializer<Integer> {
870+
@Override
871+
public void serialize(Encoder encoder, EncoderContext context, Argument<? extends Integer> type, Integer value) throws IOException {
872+
encoder.encodeInt(123);
873+
}
874+
}
875+
''')
876+
877+
expect:
878+
jsonMapper.writeValueAsString(typeUnderTest.type.newInstance(1)) == """{"value":123}"""
879+
jsonMapper.writeValueAsString(typeUnderTest.type.newInstance(2)) == """{"value":123}"""
880+
jsonMapper.writeValueAsString(typeUnderTest.type.newInstance(3)) == """{"value":123}"""
881+
882+
cleanup:
883+
context.close()
884+
}
730885
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2017-2025 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.serde.processor.serde;
17+
18+
import io.micronaut.core.annotation.AnnotationValue;
19+
import io.micronaut.core.annotation.AnnotationValueBuilder;
20+
import io.micronaut.inject.annotation.TypedAnnotationMapper;
21+
import io.micronaut.inject.visitor.VisitorContext;
22+
import io.micronaut.serde.annotation.Serdeable;
23+
import io.micronaut.serde.config.annotation.SerdeConfig;
24+
import java.util.List;
25+
26+
public final class DeserializableMapper
27+
implements TypedAnnotationMapper<Serdeable.Deserializable> {
28+
@Override
29+
public Class<Serdeable.Deserializable> annotationType() {
30+
return Serdeable.Deserializable.class;
31+
}
32+
33+
@Override
34+
public List<AnnotationValue<?>> map(AnnotationValue<Serdeable.Deserializable> annotation, VisitorContext visitorContext) {
35+
AnnotationValueBuilder<SerdeConfig> builder = AnnotationValue.builder(SerdeConfig.class);
36+
annotation.annotationClassValue("as").ifPresent(as ->
37+
builder
38+
.member(SerdeConfig.DESERIALIZE_AS, as)
39+
);
40+
annotation.annotationClassValue("using").ifPresent(using ->
41+
builder
42+
.member(SerdeConfig.DESERIALIZER_CLASS, using)
43+
);
44+
annotation.booleanValue("validate").ifPresent(validation ->
45+
builder
46+
.member(SerdeConfig.VALIDATE, validation)
47+
);
48+
annotation.annotationClassValue("naming").ifPresent(naming ->
49+
builder
50+
.member(SerdeConfig.NAMING, naming)
51+
);
52+
53+
return List.of(builder.build());
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2017-2025 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.serde.processor.serde;
17+
18+
import io.micronaut.core.annotation.AnnotationValue;
19+
import io.micronaut.core.annotation.AnnotationValueBuilder;
20+
import io.micronaut.inject.annotation.TypedAnnotationMapper;
21+
import io.micronaut.inject.visitor.VisitorContext;
22+
import io.micronaut.serde.annotation.Serdeable;
23+
import io.micronaut.serde.config.annotation.SerdeConfig;
24+
import java.util.List;
25+
26+
public final class SerdeableMapper
27+
implements TypedAnnotationMapper<Serdeable> {
28+
@Override
29+
public Class<Serdeable> annotationType() {
30+
return Serdeable.class;
31+
}
32+
33+
@Override
34+
public List<AnnotationValue<?>> map(AnnotationValue<Serdeable> annotation, VisitorContext visitorContext) {
35+
36+
AnnotationValueBuilder<SerdeConfig> builder = AnnotationValue.builder(SerdeConfig.class);
37+
annotation.booleanValue("validate").ifPresent(validation ->
38+
builder
39+
.member(SerdeConfig.VALIDATE, validation)
40+
);
41+
annotation.annotationClassValue("naming").ifPresent(naming ->
42+
builder
43+
.member(SerdeConfig.NAMING, naming)
44+
);
45+
46+
AnnotationValue<SerdeConfig> result = builder.build();
47+
if (!result.getValues().isEmpty()) {
48+
return List.of(result);
49+
} else {
50+
return List.of();
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)