@@ -47,6 +47,13 @@ public class UntypedObjectDeserializer
47
47
48
48
protected JsonDeserializer <Object > _numberDeserializer ;
49
49
50
+ /**
51
+ * Object.class may also have custom key deserializer
52
+ *
53
+ * @since 2.19
54
+ */
55
+ private KeyDeserializer _customKeyDeserializer ;
56
+
50
57
/**
51
58
* If {@link java.util.List} has been mapped to non-default implementation,
52
59
* we'll store type here
@@ -73,7 +80,7 @@ public class UntypedObjectDeserializer
73
80
*/
74
81
@ Deprecated
75
82
public UntypedObjectDeserializer () {
76
- this (null , null );
83
+ this (null , ( JavaType ) null );
77
84
}
78
85
79
86
public UntypedObjectDeserializer (JavaType listType , JavaType mapType ) {
@@ -95,6 +102,7 @@ public UntypedObjectDeserializer(UntypedObjectDeserializer base,
95
102
_numberDeserializer = (JsonDeserializer <Object >) numberDeser ;
96
103
_listType = base ._listType ;
97
104
_mapType = base ._mapType ;
105
+ _customKeyDeserializer = base ._customKeyDeserializer ;
98
106
_nonMerging = base ._nonMerging ;
99
107
}
100
108
@@ -111,9 +119,27 @@ protected UntypedObjectDeserializer(UntypedObjectDeserializer base,
111
119
_numberDeserializer = base ._numberDeserializer ;
112
120
_listType = base ._listType ;
113
121
_mapType = base ._mapType ;
122
+ _customKeyDeserializer = base ._customKeyDeserializer ;
114
123
_nonMerging = nonMerging ;
115
124
}
116
125
126
+ /**
127
+ * @since 2.19
128
+ */
129
+ protected UntypedObjectDeserializer (UntypedObjectDeserializer base ,
130
+ KeyDeserializer keyDeser )
131
+ {
132
+ super (Object .class );
133
+ _mapDeserializer = base ._mapDeserializer ;
134
+ _listDeserializer = base ._listDeserializer ;
135
+ _stringDeserializer = base ._stringDeserializer ;
136
+ _numberDeserializer = base ._numberDeserializer ;
137
+ _listType = base ._listType ;
138
+ _mapType = base ._mapType ;
139
+ _nonMerging = base ._nonMerging ;
140
+ _customKeyDeserializer = keyDeser ;
141
+ }
142
+
117
143
/*
118
144
/**********************************************************
119
145
/* Initialization
@@ -190,19 +216,32 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
190
216
// 14-Jun-2017, tatu: [databind#1625]: may want to block merging, for root value
191
217
boolean preventMerge = (property == null )
192
218
&& Boolean .FALSE .equals (ctxt .getConfig ().getDefaultMergeable (Object .class ));
219
+ // Since 2.19, 31-Aug-2024: [databind#4680] Allow custom key deserializer for Object.class
220
+ KeyDeserializer customKeyDeser = ctxt .findKeyDeserializer (ctxt .constructType (Object .class ), property );
221
+ // but make sure to ignore standard/default key deserializer (perf optimization)
222
+ if (customKeyDeser != null ) {
223
+ if (ClassUtil .isJacksonStdImpl (customKeyDeser )) {
224
+ customKeyDeser = null ;
225
+ }
226
+ }
193
227
// 20-Apr-2014, tatu: If nothing custom, let's use "vanilla" instance,
194
228
// simpler and can avoid some of delegation
195
229
if ((_stringDeserializer == null ) && (_numberDeserializer == null )
196
230
&& (_mapDeserializer == null ) && (_listDeserializer == null )
231
+ && (customKeyDeser == null ) // [databind#4680] Since 2.19 : Allow custom key deserializer for Object.class
197
232
&& getClass () == UntypedObjectDeserializer .class ) {
198
233
return UntypedObjectDeserializerNR .instance (preventMerge );
199
234
}
200
235
236
+ UntypedObjectDeserializer deser = this ;
201
237
if (preventMerge != _nonMerging ) {
202
- return new UntypedObjectDeserializer (this , preventMerge );
238
+ deser = new UntypedObjectDeserializer (deser , preventMerge );
203
239
}
204
-
205
- return this ;
240
+ // [databind#4680] Since 2.19 : Allow custom key deserializer for Object.class
241
+ if (customKeyDeser != null ) {
242
+ deser = new UntypedObjectDeserializer (deser , customKeyDeser );
243
+ }
244
+ return deser ;
206
245
}
207
246
208
247
/*
@@ -496,6 +535,7 @@ protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOE
496
535
// empty map might work; but caller may want to modify... so better just give small modifiable
497
536
return new LinkedHashMap <>(2 );
498
537
}
538
+ key1 = _customDeserializeKey (key1 , ctxt );
499
539
// minor optimization; let's handle 1 and 2 entry cases separately
500
540
// 24-Mar-2015, tatu: Ideally, could use one of 'nextXxx()' methods, but for
501
541
// that we'd need new method(s) in JsonDeserializer. So not quite yet.
@@ -508,6 +548,8 @@ protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOE
508
548
result .put (key1 , value1 );
509
549
return result ;
510
550
}
551
+ key2 = _customDeserializeKey (key2 , ctxt );
552
+
511
553
p .nextToken ();
512
554
Object value2 = deserialize (p , ctxt );
513
555
@@ -521,6 +563,8 @@ protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOE
521
563
}
522
564
return result ;
523
565
}
566
+ key = _customDeserializeKey (key , ctxt );
567
+
524
568
// And then the general case; default map size is 16
525
569
LinkedHashMap <String , Object > result = new LinkedHashMap <>();
526
570
result .put (key1 , value1 );
@@ -535,9 +579,9 @@ protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOE
535
579
final Object oldValue = result .put (key , newValue );
536
580
if (oldValue != null ) {
537
581
return _mapObjectWithDups (p , ctxt , result , key , oldValue , newValue ,
538
- p .nextFieldName ());
582
+ _customDeserializeNullableKey ( p .nextFieldName (), ctxt ));
539
583
}
540
- } while ((key = p .nextFieldName ()) != null );
584
+ } while ((key = _customDeserializeNullableKey ( p .nextFieldName (), ctxt )) != null );
541
585
return result ;
542
586
}
543
587
@@ -559,12 +603,44 @@ protected Object _mapObjectWithDups(JsonParser p, DeserializationContext ctxt,
559
603
if ((oldValue != null ) && squashDups ) {
560
604
_squashDups (result , key , oldValue , newValue );
561
605
}
562
- nextKey = p .nextFieldName ();
606
+ nextKey = _customDeserializeNullableKey ( p .nextFieldName (), ctxt );
563
607
}
564
608
565
609
return result ;
566
610
}
567
611
612
+ /**
613
+ * Helper function to allow custom key deserialization without null handling.
614
+ * Similar to {@link #_customDeserializeNullableKey(String, DeserializationContext)}, but
615
+ * null handling is done by the caller.
616
+ *
617
+ * @returns Custom-deserialized key if both custom key deserializer is set.
618
+ * Otherwise the original key.
619
+ */
620
+ private final String _customDeserializeKey (String key , DeserializationContext ctxt ) throws IOException {
621
+ if (_customKeyDeserializer != null ) {
622
+ return (String ) _customKeyDeserializer .deserializeKey (key , ctxt );
623
+ }
624
+ return key ;
625
+ }
626
+
627
+ /**
628
+ * Helper function to allow custom key deserialization with null handling.
629
+ * Similar to {@link #_customDeserializeKey(String, DeserializationContext)}, but instead
630
+ * only returns custom-deserialized key if key is not null.
631
+ *
632
+ * @returns Custom-deserialized key if both custom key deserializer is set and key is not null.
633
+ * Otherwise the original key.
634
+ */
635
+ private final String _customDeserializeNullableKey (String key , DeserializationContext ctxt ) throws IOException {
636
+ if (_customKeyDeserializer != null ) {
637
+ if (key != null ) {
638
+ return (String ) _customKeyDeserializer .deserializeKey (key , ctxt );
639
+ }
640
+ }
641
+ return key ;
642
+ }
643
+
568
644
@ SuppressWarnings ("unchecked" )
569
645
private void _squashDups (final Map <String , Object > result , String key ,
570
646
Object oldValue , Object newValue )
0 commit comments