Skip to content

Commit 8dd9e6d

Browse files
authored
Correct ignored properties for a constructor injection (#773)
1 parent d1bf594 commit 8dd9e6d

File tree

4 files changed

+264
-10
lines changed

4 files changed

+264
-10
lines changed

Diff for: serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonPropertySpec.groovy

+85
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,91 @@ import spock.lang.Unroll
55

66
class JsonPropertySpec extends JsonCompileSpec {
77

8+
void "test @JsonProperty.Access.WRITE_ONLY (set only) - records"() {
9+
given:
10+
def context = buildContext("""
11+
package test;
12+
13+
import com.fasterxml.jackson.annotation.JsonProperty;
14+
import io.micronaut.serde.annotation.Serdeable;
15+
16+
@Serdeable
17+
record Test(
18+
@JsonProperty
19+
String value,
20+
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
21+
String ignored
22+
) {}
23+
""")
24+
when:
25+
def bean = newInstance(context, 'test.Test', "test", "xyz")
26+
def result = writeJson(jsonMapper, bean)
27+
28+
then:
29+
result == '{"value":"test"}'
30+
31+
when:
32+
bean = jsonMapper.readValue('{"value":"test","ignored":"xyz"}', argumentOf(context, 'test.Test'))
33+
34+
then:
35+
bean.value == 'test'
36+
bean.ignored == 'xyz'
37+
38+
cleanup:
39+
context.close()
40+
}
41+
42+
void "test @JsonProperty.Access.WRITE_ONLY (set only) - constructor"() {
43+
given:
44+
def context = buildContext("""
45+
package test;
46+
47+
import com.fasterxml.jackson.annotation.JsonCreator;
48+
import com.fasterxml.jackson.annotation.JsonProperty;
49+
import io.micronaut.serde.annotation.Serdeable;
50+
51+
@Serdeable
52+
class Test {
53+
54+
private String value;
55+
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
56+
private String ignored;
57+
58+
@JsonCreator
59+
public Test(@JsonProperty("value") String value, @JsonProperty("ignored") String ignored) {
60+
this.value = value;
61+
this.ignored = ignored;
62+
}
63+
64+
public String getValue() {
65+
return this.value;
66+
}
67+
68+
public String getIgnored() {
69+
return this.ignored;
70+
}
71+
72+
}
73+
""")
74+
when:
75+
def bean = newInstance(context, 'test.Test', "test", "xyz")
76+
def result = writeJson(jsonMapper, bean)
77+
78+
then:
79+
result == '{"value":"test"}'
80+
81+
when:
82+
bean = jsonMapper.readValue('{"value":"test","ignored":"xyz"}', argumentOf(context, 'test.Test'))
83+
84+
then:
85+
bean.value == 'test'
86+
bean.ignored == 'xyz'
87+
88+
cleanup:
89+
context.close()
90+
}
91+
92+
893
@Unroll
994
void "serde Number"(Number number) {
1095
given:

Diff for: serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonPropertySpec.groovy

+88-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,92 @@ import spock.lang.PendingFeature
66

77
class SerdeJsonPropertySpec extends JsonPropertySpec {
88

9+
void "test @JsonProperty.Access.READ_ONLY (get only) - constructor"() {
10+
// Jackson cannot deserialize READ_ONLY as null
11+
given:
12+
def context = buildContext("""
13+
package test;
14+
15+
import com.fasterxml.jackson.annotation.JsonCreator;
16+
import com.fasterxml.jackson.annotation.JsonProperty;
17+
import io.micronaut.serde.annotation.Serdeable;
18+
19+
@Serdeable
20+
class Test {
21+
22+
private String value;
23+
private String ignored;
24+
25+
@JsonCreator
26+
public Test(@JsonProperty("value") String value, @JsonProperty(value = "ignored", access = JsonProperty.Access.READ_ONLY) String ignored) {
27+
this.value = value;
28+
this.ignored = ignored;
29+
}
30+
31+
public String getValue() {
32+
return this.value;
33+
}
34+
35+
public String getIgnored() {
36+
return this.ignored;
37+
}
38+
39+
}
40+
""")
41+
when:
42+
def bean = newInstance(context, 'test.Test', "test", "xyz")
43+
def result = writeJson(jsonMapper, bean)
44+
45+
then:
46+
result == '{"value":"test","ignored":"xyz"}'
47+
48+
when:
49+
bean = jsonMapper.readValue('{"value":"test","ignored":"xyz"}', argumentOf(context, 'test.Test'))
50+
51+
then:
52+
bean.value == 'test'
53+
bean.ignored == null
54+
55+
cleanup:
56+
context.close()
57+
}
58+
59+
void "test @JsonProperty.Access.READ_ONLY (get only) - record"() {
60+
// Jackson cannot deserialize READ_ONLY as null
61+
given:
62+
def context = buildContext("""
63+
package test;
64+
65+
import com.fasterxml.jackson.annotation.JsonCreator;
66+
import com.fasterxml.jackson.annotation.JsonProperty;
67+
import io.micronaut.serde.annotation.Serdeable;
68+
69+
@Serdeable
70+
record Test(
71+
@JsonProperty
72+
String value,
73+
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
74+
String ignored
75+
) {}
76+
""")
77+
when:
78+
def bean = newInstance(context, 'test.Test', "test", "xyz")
79+
def result = writeJson(jsonMapper, bean)
80+
81+
then:
82+
result == '{"value":"test","ignored":"xyz"}'
83+
84+
when:
85+
bean = jsonMapper.readValue('{"value":"test","ignored":"xyz"}', argumentOf(context, 'test.Test'))
86+
87+
then:
88+
bean.value == 'test'
89+
bean.ignored == null
90+
91+
cleanup:
92+
context.close()
93+
}
94+
995
void "test optional by default primitive field in constructor XXX"() {
1096

1197
given:
@@ -238,7 +324,7 @@ import io.micronaut.serde.annotation.Serdeable;
238324
record Test(
239325
@JsonProperty(value = "other", defaultValue = "default")
240326
String value,
241-
@JsonProperty(access = JsonProperty.Access.READ_ONLY, defaultValue = "false")
327+
@JsonProperty(access = JsonProperty.Access.READ_ONLY, defaultValue = "false") // Get only
242328
boolean ignored
243329
) {}
244330
""")
@@ -250,7 +336,7 @@ record Test(
250336
result == '{"other":"test","ignored":false}'
251337

252338
when:
253-
bean = jsonMapper.readValue(result, argumentOf(context, 'test.Test'))
339+
bean = jsonMapper.readValue('{"other":"test","ignored":true}', argumentOf(context, 'test.Test'))
254340

255341
then:
256342
bean.ignored == false

Diff for: serde-support/src/main/java/io/micronaut/serde/support/deserializers/DeserBean.java

+3-8
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,6 @@ public DeserBean(DeserializationConfiguration defaultDeserializationConfiguratio
172172
for (int i = 0; i < constructorArguments.length; i++) {
173173
Argument<Object> constructorArgument = resolveArgument((Argument<Object>) constructorArguments[i]);
174174
final AnnotationMetadata annotationMetadata = resolveArgumentMetadata(introspection, constructorArgument, constructorArgument.getAnnotationMetadata());
175-
if (annotationMetadata.isTrue(SerdeConfig.class, SerdeConfig.IGNORED)
176-
|| annotationMetadata.isTrue(SerdeConfig.class, SerdeConfig.IGNORED_DESERIALIZATION)) {
177-
continue;
178-
}
179175
if (annotationMetadata.isAnnotationPresent(SerdeConfig.SerAnySetter.class)) {
180176
anySetterValue = new AnySetter<>(constructorArgument, i);
181177
continue;
@@ -189,8 +185,8 @@ public DeserBean(DeserializationConfiguration defaultDeserializationConfiguratio
189185
PropertyNamingStrategy propertyNamingStrategy = getPropertyNamingStrategy(annotationMetadata, decoderContext, entityPropertyNamingStrategy);
190186
final String propertyName = resolveName(serdeArgumentConf, constructorArgument, annotationMetadata, propertyNamingStrategy);
191187

192-
if (allowPropertyPredicate != null && !allowPropertyPredicate.test(propertyName)) {
193-
continue;
188+
if (isIgnored(annotationMetadata) || (allowPropertyPredicate != null && !allowPropertyPredicate.test(propertyName))) {
189+
ignoredProperties.add(propertyName);
194190
}
195191

196192
Argument<Object> constructorWithPropertyArgument = Argument.of(
@@ -608,8 +604,7 @@ private static <T> Deserializer<T> findDeserializer(Deserializer.DecoderContext
608604
return (Deserializer<T>) decoderContext.findDeserializer(argument).createSpecific(decoderContext, argument);
609605
}
610606

611-
private boolean isIgnored(BeanProperty<T, Object> bp) {
612-
final AnnotationMetadata annotationMetadata = bp.getAnnotationMetadata();
607+
private boolean isIgnored(AnnotationMetadata annotationMetadata) {
613608
return annotationMetadata.booleanValue(SerdeConfig.class, SerdeConfig.READ_ONLY).orElse(false)
614609
|| annotationMetadata.booleanValue(SerdeConfig.class, SerdeConfig.IGNORED).orElse(false)
615610
|| annotationMetadata.booleanValue(SerdeConfig.class, SerdeConfig.IGNORED_DESERIALIZATION).orElse(false);

Diff for: test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindJsonPropertySpec.groovy

+88
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,94 @@ class DatabindJsonPropertySpec extends JsonPropertySpec {
1313
))
1414
}
1515

16+
@PendingFeature
17+
void "test @JsonProperty.Access.READ_ONLY (get only) - constructor"() {
18+
// Jackson cannot deserialize READ_ONLY as null
19+
given:
20+
def context = buildContext("""
21+
package test;
22+
23+
import com.fasterxml.jackson.annotation.JsonCreator;
24+
import com.fasterxml.jackson.annotation.JsonProperty;
25+
import io.micronaut.serde.annotation.Serdeable;
26+
27+
@Serdeable
28+
class Test {
29+
30+
private String value;
31+
private String ignored;
32+
33+
@JsonCreator
34+
public Test(@JsonProperty("value") String value, @JsonProperty(value = "ignored", access = JsonProperty.Access.READ_ONLY) String ignored) {
35+
this.value = value;
36+
this.ignored = ignored;
37+
}
38+
39+
public String getValue() {
40+
return this.value;
41+
}
42+
43+
public String getIgnored() {
44+
return this.ignored;
45+
}
46+
47+
}
48+
""")
49+
when:
50+
def bean = newInstance(context, 'test.Test', "test", "xyz")
51+
def result = writeJson(jsonMapper, bean)
52+
53+
then:
54+
result == '{"value":"test","ignored":"xyz"}'
55+
56+
when:
57+
bean = jsonMapper.readValue('{"value":"test","ignored":"xyz"}', argumentOf(context, 'test.Test'))
58+
59+
then:
60+
bean.value == 'test'
61+
bean.ignored == null
62+
63+
cleanup:
64+
context.close()
65+
}
66+
67+
@PendingFeature
68+
void "test @JsonProperty.Access.READ_ONLY (get only) - record"() {
69+
// Jackson cannot deserialize READ_ONLY as null
70+
given:
71+
def context = buildContext("""
72+
package test;
73+
74+
import com.fasterxml.jackson.annotation.JsonCreator;
75+
import com.fasterxml.jackson.annotation.JsonProperty;
76+
import io.micronaut.serde.annotation.Serdeable;
77+
78+
@Serdeable
79+
record Test(
80+
@JsonProperty
81+
String value,
82+
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
83+
String ignored
84+
) {}
85+
""")
86+
when:
87+
def bean = newInstance(context, 'test.Test', "test", "xyz")
88+
def result = writeJson(jsonMapper, bean)
89+
90+
then:
91+
result == '{"value":"test","ignored":"xyz"}'
92+
93+
when:
94+
bean = jsonMapper.readValue('{"value":"test","ignored":"xyz"}', argumentOf(context, 'test.Test'))
95+
96+
then:
97+
bean.value == 'test'
98+
bean.ignored == null
99+
100+
cleanup:
101+
context.close()
102+
}
103+
16104
void "test required primitive field"() {
17105

18106
given:

0 commit comments

Comments
 (0)