Skip to content

Commit eeb5f2b

Browse files
authored
Refactoring JsonFormat detection, post #4409 (#4418)
1 parent ef34d13 commit eeb5f2b

File tree

12 files changed

+124
-42
lines changed

12 files changed

+124
-42
lines changed

release-notes/CREDITS-2.x

+10-5
Original file line numberDiff line numberDiff line change
@@ -1730,20 +1730,25 @@ Jan Pachol (janpacho@github)
17301730
(2.16.0)
17311731

17321732
Pieter Dirk Soels (Badbond@github)
1733-
* Reprted #4302: Problem deserializing some type of Enums when using
1733+
* Reported #4302: Problem deserializing some type of Enums when using
17341734
`PropertyNamingStrategy`
17351735
(2.16.2)
17361736

1737+
Stephane Bailliez (sbailliez@github)
1738+
* Reported #4409: Deserialization of enums with name defined with different cases
1739+
leads to `InvalidDefinitionException`: Multiple fields representing property
1740+
(2.16.2)
1741+
17371742
Muhammad Khalikov (mukham12@github)
17381743
* Contributed fix for #4209: Make `BeanDeserializerModifier`/`BeanSerializerModifier`
1739-
implement `java.io.Serializable`
1740-
(2.17.0)
1744+
implement `java.io.Serializable`
1745+
(2.17.0)
17411746

17421747
Eduard Dudar (edudar@github)
17431748
* Contributed #4299: Some `Collection` and `Map` fallbacks don't work in GraalVM native image
1744-
(2.17.0)
1749+
(2.17.0)
17451750
17461751
Jesper Blomquist (jebl01@github)
17471752
* Contributed #4393: Deserialize `java.util.UUID` encoded as Base64 and base64Url with or
17481753
without padding
1749-
(2.17.0)
1754+
(2.17.0)

release-notes/VERSION-2.x

+4
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ Project: jackson-databind
6161
#4355: Jackson 2.16 fails attempting to obtain `ObjectWriter` for an `Enum` of which
6262
some value returns null from `toString()`
6363
(reported by @YutaHiguchi-bsn)
64+
#4409: Deserialization of enums with name defined with different cases leads to
65+
`InvalidDefinitionException`: Multiple fields representing property
66+
(reported by Stephane B)
67+
(fix contributed by Joo-Hyuk K)
6468

6569
2.16.1 (24-Dec-2023)
6670

src/main/java/com/fasterxml/jackson/databind/BeanDescription.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -305,9 +305,25 @@ public AnnotatedMember findAnySetterField() {
305305
* defined by defaults and possible annotations.
306306
* Note that this may be further refined by per-property annotations.
307307
*
308+
* @since 2.17
309+
*/
310+
public abstract JsonFormat.Value findExpectedFormat();
311+
312+
/**
308313
* @since 2.1
314+
* @deprecated Since 2.17 use {@link #findExpectedFormat()}
309315
*/
310-
public abstract JsonFormat.Value findExpectedFormat(JsonFormat.Value defValue);
316+
@Deprecated // since 2.17
317+
public JsonFormat.Value findExpectedFormat(JsonFormat.Value defValue) {
318+
JsonFormat.Value v = findExpectedFormat();
319+
if (defValue == null) {
320+
return v;
321+
}
322+
if (v == null) {
323+
return defValue;
324+
}
325+
return defValue.withOverrides(v);
326+
}
311327

312328
/**
313329
* Method for finding {@link Converter} used for serializing instances

src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ protected BeanDeserializerBase(BeanDeserializerBuilder builder,
251251
;
252252

253253
// Any transformation we may need to apply?
254-
final JsonFormat.Value format = beanDesc.findExpectedFormat(null);
254+
final JsonFormat.Value format = beanDesc.findExpectedFormat();
255255
_serializationShape = format.getShape();
256256

257257
_needViewProcesing = hasViews;

src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ protected Map<String,List<PropertyName>> _collectAliases(Collection<SettableBean
596596
protected boolean _findCaseInsensitivity() {
597597
// 07-May-2020, tatu: First find combination of per-type config overrides (higher
598598
// precedence) and per-type annotations (lower):
599-
JsonFormat.Value format = _beanDesc.findExpectedFormat(null);
599+
JsonFormat.Value format = _beanDesc.findExpectedFormat();
600600
// and see if any of those has explicit definition; if not, use global baseline default
601601
Boolean B = format.getFeature(JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
602602
return (B == null) ? _config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)

src/main/java/com/fasterxml/jackson/databind/deser/DeserializerCache.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ protected JsonDeserializer<?> _createDeserializer2(DeserializationContext ctxt,
406406
// Ideally we'd determine it bit later on (to allow custom handler checks)
407407
// but that won't work for other reasons. So do it here.
408408
// (read: rewrite for 3.0)
409-
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
409+
JsonFormat.Value format = beanDesc.findExpectedFormat();
410410
if (format.getShape() != JsonFormat.Shape.OBJECT) {
411411
MapLikeType mlt = (MapLikeType) type;
412412
if (mlt instanceof MapType) {
@@ -421,7 +421,7 @@ protected JsonDeserializer<?> _createDeserializer2(DeserializationContext ctxt,
421421
* (to allow custom handler checks), but that won't work for other
422422
* reasons. So do it here.
423423
*/
424-
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
424+
JsonFormat.Value format = beanDesc.findExpectedFormat();
425425
if (format.getShape() != JsonFormat.Shape.OBJECT) {
426426
CollectionLikeType clt = (CollectionLikeType) type;
427427
if (clt instanceof CollectionType) {

src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java

+5-22
Original file line numberDiff line numberDiff line change
@@ -405,30 +405,13 @@ public AnnotatedMethod findMethod(String name, Class<?>[] paramTypes) {
405405
/**********************************************************
406406
*/
407407

408-
@Override
409-
public JsonFormat.Value findExpectedFormat(JsonFormat.Value defValue)
408+
@Override // since 2.17
409+
public JsonFormat.Value findExpectedFormat()
410410
{
411-
// 15-Apr-2016, tatu: Let's check both per-type defaults and annotations; per-type
412-
// defaults having higher precedence, so start with that
413-
if (_annotationIntrospector != null) {
414-
JsonFormat.Value v = _annotationIntrospector.findFormat(_classInfo);
415-
if (v != null) {
416-
if (defValue == null) {
417-
defValue = v;
418-
} else {
419-
defValue = defValue.withOverrides(v);
420-
}
421-
}
411+
if (_propCollector == null) {
412+
return JsonFormat.Value.empty();
422413
}
423-
JsonFormat.Value v = _config.getDefaultPropertyFormat(_classInfo.getRawType());
424-
if (v != null) {
425-
if (defValue == null) {
426-
defValue = v;
427-
} else {
428-
defValue = defValue.withOverrides(v);
429-
}
430-
}
431-
return defValue;
414+
return _propCollector.getFormatOverrides();
432415
}
433416

434417
@Override // since 2.9

src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java

+39
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.fasterxml.jackson.annotation.JacksonInject;
77
import com.fasterxml.jackson.annotation.JsonCreator;
88

9+
import com.fasterxml.jackson.annotation.JsonFormat;
910
import com.fasterxml.jackson.databind.*;
1011
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
1112
import com.fasterxml.jackson.databind.cfg.MapperConfig;
@@ -144,6 +145,13 @@ public class POJOPropertiesCollector
144145
*/
145146
protected LinkedHashMap<Object, AnnotatedMember> _injectables;
146147

148+
/**
149+
* Lazily accessed information about POJO format overrides
150+
*
151+
* @since 2.17
152+
*/
153+
protected JsonFormat.Value _formatOverrides;
154+
147155
// // // Deprecated entries to remove from 3.0
148156

149157
/**
@@ -421,6 +429,31 @@ public Class<?> findPOJOBuilderClass() {
421429
return _annotationIntrospector.findPOJOBuilder(_classDef);
422430
}
423431

432+
/**
433+
* @since 2.17
434+
*/
435+
public JsonFormat.Value getFormatOverrides() {
436+
if (_formatOverrides == null) {
437+
JsonFormat.Value format = null;
438+
439+
// Let's check both per-type defaults and annotations;
440+
// per-type defaults having higher precedence, so start with annotations
441+
if (_annotationIntrospector != null) {
442+
format = _annotationIntrospector.findFormat(_classDef);
443+
}
444+
JsonFormat.Value v = _config.getDefaultPropertyFormat(_type.getRawClass());
445+
if (v != null) {
446+
if (format == null) {
447+
format = v;
448+
} else {
449+
format = format.withOverrides(v);
450+
}
451+
}
452+
_formatOverrides = (format == null) ? JsonFormat.Value.empty() : format;
453+
}
454+
return _formatOverrides;
455+
}
456+
424457
/*
425458
/**********************************************************
426459
/* Public API: main-level collection
@@ -1123,6 +1156,12 @@ protected void _renameProperties(Map<String, POJOPropertyBuilder> props)
11231156
protected void _renameUsing(Map<String, POJOPropertyBuilder> propMap,
11241157
PropertyNamingStrategy naming)
11251158
{
1159+
// [databind#4409]: Need to skip renaming for Enums, unless Enums are handled as OBJECT format
1160+
if (_type.isEnumType()) {
1161+
if (getFormatOverrides().getShape() != JsonFormat.Shape.OBJECT) {
1162+
return;
1163+
}
1164+
}
11261165
POJOPropertyBuilder[] props = propMap.values().toArray(new POJOPropertyBuilder[propMap.size()]);
11271166
propMap.clear();
11281167
for (POJOPropertyBuilder prop : props) {

src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
import java.util.*;
44
import java.util.stream.Collectors;
55

6-
import com.fasterxml.jackson.annotation.JsonInclude;
7-
import com.fasterxml.jackson.annotation.JsonProperty;
8-
import com.fasterxml.jackson.annotation.JsonSetter;
9-
import com.fasterxml.jackson.annotation.Nulls;
6+
import com.fasterxml.jackson.annotation.*;
107

118
import com.fasterxml.jackson.databind.*;
129
import com.fasterxml.jackson.databind.cfg.ConfigOverride;

src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ protected final JsonSerializer<?> findSerializerByPrimaryType(SerializerProvider
467467
}
468468
if (Number.class.isAssignableFrom(raw)) {
469469
// 21-May-2014, tatu: Couple of alternatives actually
470-
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
470+
JsonFormat.Value format = beanDesc.findExpectedFormat();
471471
switch (format.getShape()) {
472472
case STRING:
473473
return ToStringSerializer.instance;
@@ -713,7 +713,7 @@ protected JsonSerializer<?> buildCollectionSerializer(SerializerProvider prov,
713713
if (ser == null) {
714714
// We may also want to use serialize Collections "as beans", if (and only if)
715715
// this is specified with `@JsonFormat(shape=Object)`
716-
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
716+
JsonFormat.Value format = beanDesc.findExpectedFormat();
717717
if (format.getShape() == JsonFormat.Shape.OBJECT) {
718718
return null;
719719
}
@@ -803,7 +803,7 @@ protected JsonSerializer<?> buildMapSerializer(SerializerProvider prov,
803803
{
804804
// [databind#467]: This is where we could allow serialization "as POJO": But! It's
805805
// nasty to undo, and does not apply on per-property basis. So, hardly optimal
806-
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
806+
JsonFormat.Value format = beanDesc.findExpectedFormat();
807807
if (format.getShape() == JsonFormat.Shape.OBJECT) {
808808
return null;
809809
}
@@ -924,7 +924,7 @@ protected JsonSerializer<?> buildMapEntrySerializer(SerializerProvider prov,
924924
// [databind#865]: Allow serialization "as POJO" -- note: to undo, declare
925925
// serialization as `Shape.NATURAL` instead; that's JSON Object too.
926926
JsonFormat.Value formatOverride = prov.getDefaultPropertyFormat(Map.Entry.class);
927-
JsonFormat.Value formatFromAnnotation = beanDesc.findExpectedFormat(null);
927+
JsonFormat.Value formatFromAnnotation = beanDesc.findExpectedFormat();
928928
JsonFormat.Value format = JsonFormat.Value.merge(formatFromAnnotation, formatOverride);
929929
if (format.getShape() == JsonFormat.Shape.OBJECT) {
930930
return null;
@@ -1202,7 +1202,7 @@ protected JsonSerializer<?> buildEnumSerializer(SerializationConfig config,
12021202
* POJO style serialization, so we must handle that special case separately;
12031203
* otherwise pass it to EnumSerializer.
12041204
*/
1205-
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
1205+
JsonFormat.Value format = beanDesc.findExpectedFormat();
12061206
if (format.getShape() == JsonFormat.Shape.OBJECT) {
12071207
// one special case: suppress serialization of "getDeclaringClass()"...
12081208
((BasicBeanDescription) beanDesc).removeProperty("declaringClass");

src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ protected BeanSerializerBase(JavaType type, BeanSerializerBuilder builder,
128128
_anyGetterWriter = builder.getAnyGetter();
129129
_propertyFilterId = builder.getFilterId();
130130
_objectIdWriter = builder.getObjectIdWriter();
131-
final JsonFormat.Value format = builder.getBeanDescription().findExpectedFormat(null);
131+
final JsonFormat.Value format = builder.getBeanDescription().findExpectedFormat();
132132
_serializationShape = format.getShape();
133133
}
134134
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.fasterxml.jackson.databind.deser.enums;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import com.fasterxml.jackson.databind.DeserializationFeature;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
8+
9+
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
11+
import static com.fasterxml.jackson.databind.BaseMapTest.jsonMapperBuilder;
12+
13+
// [databind#4409]: PropertyNamingStrategy should not affect to Enums
14+
class EnumWithNamingStrategy4409Test {
15+
16+
enum ColorMode {
17+
RGB,
18+
RGBa,
19+
RGBA
20+
}
21+
22+
static class Bug {
23+
public ColorMode colorMode;
24+
}
25+
26+
@Test
27+
public void testEnumAndPropertyNamingStrategy() throws Exception {
28+
ObjectMapper mapper = jsonMapperBuilder()
29+
.propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
30+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
31+
.build();
32+
33+
Bug bug = mapper.readValue("{ \"color_mode\": \"RGBa\"}", Bug.class);
34+
35+
// fails
36+
assertEquals(ColorMode.RGBa, bug.colorMode);
37+
}
38+
}

0 commit comments

Comments
 (0)