Skip to content

Commit 2289df2

Browse files
committed
fix(serialization): avoid wrapping @JsonAnyGetter properties
The previous check used writer.getName().equals("additionalProperties"), which failed for variations like getAdditionalProperties or when @JsonProperty was used. Changes: - Skip wrapping the any-getter property in BeanSerializerModifier - Add isAnyGetterWriter(...) to match the correct property by member Resolved test failures: io.fabric8.kubernetes.client.utils.SerializationAdditionalPropertiesTest - marshalWithAdditionalPropertiesOverridingFields - cloneShouldPreserveAdditionalProperties
1 parent e8ea8c3 commit 2289df2

File tree

1 file changed

+42
-6
lines changed

1 file changed

+42
-6
lines changed

kubernetes-model-generator/kubernetes-model-common/src/main/java/io/fabric8/kubernetes/model/jackson/UnmatchedFieldTypeModule.java

+42-6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.fasterxml.jackson.databind.ser.BeanSerializerBuilder;
2727
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
2828

29+
import java.lang.reflect.Member;
2930
import java.util.List;
3031
import java.util.stream.Collectors;
3132

@@ -64,23 +65,40 @@ public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanD
6465
}
6566
});
6667
setSerializerModifier(new BeanSerializerModifier() {
68+
/**
69+
* Customizes property writers used during serialization.
70+
*
71+
* This method wraps standard property writers in a delegate that can suppress serialization
72+
* of fields that may be overridden by values in the map returned by {@code @JsonAnyGetter}.
73+
*
74+
* The property corresponding to the {@code @JsonAnyGetter} method itself is left unmodified
75+
* to avoid interfering with Jackson’s internal handling via {@link com.fasterxml.jackson.databind.ser.AnyGetterWriter}.
76+
*
77+
* This mechanism ensures that if a field is both explicitly declared and also present in the
78+
* any-getter map, only the value from the any-getter is serialized, avoiding duplication or conflicts.
79+
*/
6780
@Override
6881
public BeanSerializerBuilder updateBuilder(SerializationConfig config, BeanDescription beanDesc,
6982
BeanSerializerBuilder builder) {
7083
AnnotatedMember anyGetter = beanDesc.findAnyGetter();
84+
Member anyGetterMember = (anyGetter != null) ? anyGetter.getMember() : null;
7185

72-
List<BeanPropertyWriter> originalWriters = builder.getProperties();
73-
74-
List<BeanPropertyWriter> newWriters = originalWriters.stream()
86+
// Wrap each property writer unless it's the any-getter (handled by Jackson separately)
87+
List<BeanPropertyWriter> customWriters = builder.getProperties().stream()
7588
.map(writer -> {
76-
if ("additionalProperties".equals(writer.getName())) {
89+
if (isAnyGetterWriter(writer, anyGetterMember)) {
90+
// Skipping wrapping for any-getter to avoid interfering with Jackson's internal handling
7791
return writer;
7892
}
79-
return new BeanPropertyWriterDelegate(writer, anyGetter, UnmatchedFieldTypeModule.this::isLogWarnings);
93+
// Wrap normal field writers with delegate to handle overrides and logging
94+
return new BeanPropertyWriterDelegate(
95+
writer,
96+
anyGetter,
97+
UnmatchedFieldTypeModule.this::isLogWarnings);
8098
})
8199
.collect(Collectors.toList());
82100

83-
builder.setProperties(newWriters);
101+
builder.setProperties(customWriters);
84102
return builder;
85103
}
86104
});
@@ -128,4 +146,22 @@ public static void removeInTemplate() {
128146
IN_TEMPLATE.remove();
129147
}
130148

149+
/**
150+
* Checks whether the given {@link BeanPropertyWriter} corresponds to the method annotated with {@code @JsonAnyGetter}.
151+
*
152+
* This is used to identify and exclude the any-getter property from custom wrapping,
153+
* since Jackson handles it separately via {@link com.fasterxml.jackson.databind.ser.AnyGetterWriter}.
154+
*
155+
* @param writer the property writer to examine
156+
* @param anyGetterMember the reflective member (method or field) marked with {@code @JsonAnyGetter}, if available
157+
* @return {@code true} if the writer represents the any-getter property; {@code false} otherwise
158+
*/
159+
private boolean isAnyGetterWriter(BeanPropertyWriter writer, Member anyGetterMember) {
160+
if (writer == null || anyGetterMember == null) {
161+
return false;
162+
}
163+
AnnotatedMember annotated = writer.getMember();
164+
Member member = (annotated != null) ? annotated.getMember() : null;
165+
return member != null && member.equals(anyGetterMember);
166+
}
131167
}

0 commit comments

Comments
 (0)