@@ -433,20 +433,23 @@ protected void collectAll()
433433 _potentialCreators = new PotentialCreators ();
434434
435435 // First: gather basic accessors
436- LinkedHashMap <String , POJOPropertyBuilder > props = new LinkedHashMap <String , POJOPropertyBuilder >();
436+ LinkedHashMap <String , POJOPropertyBuilder > props = new LinkedHashMap <>();
437437
438438 // 14-Nov-2024, tatu: Previously skipped checking fields for Records; with 2.18+ won't
439439 // (see [databind#3628], [databind#3895], [databind#3992], [databind#4626])
440440 _addFields (props ); // note: populates _fieldRenameMappings
441-
442441 _addMethods (props );
443442 // 25-Jan-2016, tatu: Avoid introspecting (constructor-)creators for non-static
444443 // inner classes, see [databind#1502]
445444 // 14-Nov-2024, tatu: Similarly need Creators for Records too (2.18+)
446445 if (!_classDef .isNonStaticInnerClass ()) {
447446 _addCreators (props );
448447 }
449-
448+ // 11-Jun-2025, tatu: [databind#5152] May need to "fix" mis-matching leading case
449+ // wrt Fields vs Accessors
450+ if (_config .isEnabled (MapperFeature .FIX_FIELD_NAME_UPPER_CASE_PREFIX )) {
451+ _fixLeadingFieldNameCase (props );
452+ }
450453 // Remove ignored properties, first; this MUST precede annotation merging
451454 // since logic relies on knowing exactly which accessor has which annotation
452455 _removeUnwantedProperties (props );
@@ -547,10 +550,9 @@ private Map<String, POJOPropertyBuilder> _putAnyGettersInTheEnd(
547550 protected void _addFields (Map <String , POJOPropertyBuilder > props )
548551 {
549552 final AnnotationIntrospector ai = _annotationIntrospector ;
550- /* 28-Mar-2013, tatu: For deserialization we may also want to remove
551- * final fields, as often they won't make very good mutators...
552- * (although, maybe surprisingly, JVM _can_ force setting of such fields!)
553- */
553+ // 28-Mar-2013, tatu: For deserialization we may also want to remove
554+ // final fields, as often they won't make very good mutators...
555+ // (although, maybe surprisingly, JVM _can_ force setting of such fields!)
554556 final boolean pruneFinalFields = !_forSerialization && !_config .isEnabled (MapperFeature .ALLOW_FINAL_FIELDS_AS_MUTATORS );
555557 final boolean transientAsIgnoral = _config .isEnabled (MapperFeature .PROPAGATE_TRANSIENT_MARKER );
556558
@@ -1318,6 +1320,97 @@ private String _checkRenameByField(String implName) {
13181320 return implName ;
13191321 }
13201322
1323+ /*
1324+ /**********************************************************
1325+ /* Internal methods; merging/fixing case-differences
1326+ /**********************************************************
1327+ */
1328+
1329+ // @since 2.20
1330+ protected void _fixLeadingFieldNameCase (Map <String , POJOPropertyBuilder > props )
1331+ {
1332+ // 11-Jun-2025, tatu: [databind#5152] May need to "fix" mis-matching leading case
1333+ // wrt Fields vs Accessors
1334+
1335+ // First: find possible candidates where:
1336+ //
1337+ // 1. Property only has Field
1338+ // 2. Field does NOT have explicit name (renaming)
1339+ // 3. Implicit name has upper-case for first and/or second character
1340+
1341+ Map <String , POJOPropertyBuilder > fieldsToCheck = null ;
1342+ for (Map .Entry <String , POJOPropertyBuilder > entry : props .entrySet ()) {
1343+ POJOPropertyBuilder prop = entry .getValue ();
1344+
1345+ // First: (1) and (2)
1346+ if (!prop .hasFieldAndNothingElse ()
1347+ || prop .isExplicitlyNamed ()) {
1348+ continue ;
1349+ }
1350+ // Second: (3)
1351+ if (!_firstOrSecondCharUpperCase (entry .getKey ())) {
1352+ continue ;
1353+ }
1354+ if (fieldsToCheck == null ) {
1355+ fieldsToCheck = new HashMap <>();
1356+ }
1357+ fieldsToCheck .put (entry .getKey (), prop );
1358+ }
1359+ /*// DEBUGGING
1360+ if (fieldsToCheck == null) {
1361+ System.err.println("_fixLeadingCase, candidates -> null; props -> "+props.keySet());
1362+ } else {
1363+ System.err.println("_fixLeadingCase, candidates -> "+fieldsToCheck);
1364+ }
1365+ */
1366+
1367+ if (fieldsToCheck == null ) {
1368+ return ;
1369+ }
1370+
1371+ for (Map .Entry <String , POJOPropertyBuilder > fieldEntry : fieldsToCheck .entrySet ()) {
1372+ Iterator <Map .Entry <String , POJOPropertyBuilder >> it = props .entrySet ().iterator ();
1373+ final POJOPropertyBuilder fieldProp = fieldEntry .getValue ();
1374+ final String fieldName = fieldEntry .getKey ();
1375+
1376+ while (it .hasNext ()) {
1377+ Map .Entry <String , POJOPropertyBuilder > propEntry = it .next ();
1378+ final POJOPropertyBuilder prop = propEntry .getValue ();
1379+
1380+ // Skip anything that has Field (can't merge)
1381+ if (prop == fieldProp || prop .hasField ()) {
1382+ continue ;
1383+ }
1384+ if (fieldName .equalsIgnoreCase (propEntry .getKey ())) {
1385+ // Remove non-Field property; add its accessors to Field one
1386+ it .remove ();
1387+ fieldProp .addAll (prop );
1388+ // Should we continue with possible other accessors?
1389+ // For now assume only one merge needed/desired
1390+ break ;
1391+ }
1392+ }
1393+ }
1394+ }
1395+
1396+ // @since 2.20
1397+ private boolean _firstOrSecondCharUpperCase (String name ) {
1398+ switch (name .length ()) {
1399+ case 0 :
1400+ return false ;
1401+ default :
1402+ if (!Character .isLowerCase (name .charAt (1 ))) {
1403+ return true ;
1404+ }
1405+ // fall through
1406+ case 1 :
1407+ if (!Character .isLowerCase (name .charAt (0 ))) {
1408+ return true ;
1409+ }
1410+ return false ;
1411+ }
1412+ }
1413+
13211414 /*
13221415 /**********************************************************
13231416 /* Internal methods; removing ignored properties
@@ -1420,6 +1513,7 @@ protected void _renameProperties(Map<String, POJOPropertyBuilder> props)
14201513 // With renaming need to do in phases: first, find properties to rename
14211514 Iterator <Map .Entry <String ,POJOPropertyBuilder >> it = props .entrySet ().iterator ();
14221515 LinkedList <POJOPropertyBuilder > renamed = null ;
1516+
14231517 while (it .hasNext ()) {
14241518 Map .Entry <String , POJOPropertyBuilder > entry = it .next ();
14251519 POJOPropertyBuilder prop = entry .getValue ();
0 commit comments