@@ -433,20 +433,23 @@ protected void collectAll()
433
433
_potentialCreators = new PotentialCreators ();
434
434
435
435
// First: gather basic accessors
436
- LinkedHashMap <String , POJOPropertyBuilder > props = new LinkedHashMap <String , POJOPropertyBuilder >();
436
+ LinkedHashMap <String , POJOPropertyBuilder > props = new LinkedHashMap <>();
437
437
438
438
// 14-Nov-2024, tatu: Previously skipped checking fields for Records; with 2.18+ won't
439
439
// (see [databind#3628], [databind#3895], [databind#3992], [databind#4626])
440
440
_addFields (props ); // note: populates _fieldRenameMappings
441
-
442
441
_addMethods (props );
443
442
// 25-Jan-2016, tatu: Avoid introspecting (constructor-)creators for non-static
444
443
// inner classes, see [databind#1502]
445
444
// 14-Nov-2024, tatu: Similarly need Creators for Records too (2.18+)
446
445
if (!_classDef .isNonStaticInnerClass ()) {
447
446
_addCreators (props );
448
447
}
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
+ }
450
453
// Remove ignored properties, first; this MUST precede annotation merging
451
454
// since logic relies on knowing exactly which accessor has which annotation
452
455
_removeUnwantedProperties (props );
@@ -547,10 +550,9 @@ private Map<String, POJOPropertyBuilder> _putAnyGettersInTheEnd(
547
550
protected void _addFields (Map <String , POJOPropertyBuilder > props )
548
551
{
549
552
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!)
554
556
final boolean pruneFinalFields = !_forSerialization && !_config .isEnabled (MapperFeature .ALLOW_FINAL_FIELDS_AS_MUTATORS );
555
557
final boolean transientAsIgnoral = _config .isEnabled (MapperFeature .PROPAGATE_TRANSIENT_MARKER );
556
558
@@ -1318,6 +1320,97 @@ private String _checkRenameByField(String implName) {
1318
1320
return implName ;
1319
1321
}
1320
1322
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
+
1321
1414
/*
1322
1415
/**********************************************************
1323
1416
/* Internal methods; removing ignored properties
@@ -1420,6 +1513,7 @@ protected void _renameProperties(Map<String, POJOPropertyBuilder> props)
1420
1513
// With renaming need to do in phases: first, find properties to rename
1421
1514
Iterator <Map .Entry <String ,POJOPropertyBuilder >> it = props .entrySet ().iterator ();
1422
1515
LinkedList <POJOPropertyBuilder > renamed = null ;
1516
+
1423
1517
while (it .hasNext ()) {
1424
1518
Map .Entry <String , POJOPropertyBuilder > entry = it .next ();
1425
1519
POJOPropertyBuilder prop = entry .getValue ();
0 commit comments