@@ -2,23 +2,76 @@ package com.fasterxml.jackson.module.scala.introspect
2
2
3
3
import com .fasterxml .jackson .annotation .JsonCreator
4
4
import com .fasterxml .jackson .databind .JacksonModule .SetupContext
5
- import com .fasterxml .jackson .databind .`type` .ClassKey
5
+ import com .fasterxml .jackson .databind .`type` .{ ClassKey , CollectionLikeType , MapLikeType , ReferenceType , SimpleType }
6
6
import com .fasterxml .jackson .databind .cfg .MapperConfig
7
7
import com .fasterxml .jackson .databind .deser ._
8
8
import com .fasterxml .jackson .databind .deser .std .StdValueInstantiator
9
9
import com .fasterxml .jackson .databind .introspect ._
10
10
import com .fasterxml .jackson .databind .util .{AccessPattern , LookupCache , SimpleLookupCache }
11
- import com .fasterxml .jackson .databind .{BeanDescription , DeserializationConfig , DeserializationContext , MapperFeature , PropertyName }
11
+ import com .fasterxml .jackson .databind .{BeanDescription , DeserializationConfig , DeserializationContext , JavaType , MapperFeature , PropertyName }
12
12
import com .fasterxml .jackson .module .scala .JacksonModule .InitializerBuilder
13
13
import com .fasterxml .jackson .module .scala .{JacksonModule , ScalaModule }
14
14
import com .fasterxml .jackson .module .scala .util .Implicits ._
15
15
16
16
import java .lang .annotation .Annotation
17
17
18
18
class ScalaAnnotationIntrospectorInstance (config : ScalaModule .Config ) extends NopAnnotationIntrospector with ValueInstantiators {
19
- private [this ] var _descriptorCache : LookupCache [ClassKey , BeanDescriptor ] =
19
+ private [this ] var _descriptorCache : LookupCache [ClassKey , BeanDescriptor ] =
20
20
new SimpleLookupCache [ClassKey , BeanDescriptor ](16 , 100 )
21
21
22
+ case class ClassHolder (valueClass : Option [Class [_]] = None )
23
+ private case class ClassOverrides (overrides : scala.collection.mutable.Map [String , ClassHolder ] = scala.collection.mutable.Map .empty)
24
+
25
+ private val overrideMap = scala.collection.mutable.Map [Class [_], ClassOverrides ]()
26
+
27
+ /**
28
+ * jackson-module-scala does not always properly handle deserialization of Options or Collections wrapping
29
+ * Scala primitives (eg Int, Long, Boolean). There are general issues with serializing and deserializing
30
+ * Scala 2 Enumerations. This function will not help with Enumerations.
31
+ * <p>
32
+ * This function is experimental and may be removed or significantly reworked in a later release.
33
+ * <p>
34
+ * These issues can be worked around by adding Jackson annotations on the affected fields.
35
+ * This function is designed to be used when it is not possible to apply Jackson annotations.
36
+ *
37
+ * @param clazz the (case) class
38
+ * @param fieldName the field name in the (case) class
39
+ * @param referencedType the referenced type of the field - for `Option[Long]` - the referenced type is `Long`
40
+ * @see [[clearRegisteredReferencedTypes() ]]
41
+ * @see [[clearRegisteredReferencedTypes(Class[_])]]
42
+ * @since 2.13.0
43
+ */
44
+ def registerReferencedValueType (clazz : Class [_], fieldName : String , referencedType : Class [_]): Unit = {
45
+ val overrides = overrideMap.getOrElseUpdate(clazz, ClassOverrides ()).overrides
46
+ overrides.get(fieldName) match {
47
+ case Some (holder) => overrides.put(fieldName, holder.copy(valueClass = Some (referencedType)))
48
+ case _ => overrides.put(fieldName, ClassHolder (valueClass = Some (referencedType)))
49
+ }
50
+ }
51
+
52
+ /**
53
+ * clears the state associated with reference types for the given class
54
+ *
55
+ * @param clazz the class for which to remove the registered reference types
56
+ * @see [[registerReferencedValueType ]]
57
+ * @see [[clearRegisteredReferencedTypes() ]]
58
+ * @since 2.13.0
59
+ */
60
+ def clearRegisteredReferencedTypes (clazz : Class [_]): Unit = {
61
+ overrideMap.remove(clazz)
62
+ }
63
+
64
+ /**
65
+ * clears all the state associated with reference types
66
+ *
67
+ * @see [[registerReferencedValueType ]]
68
+ * @see [[clearRegisteredReferencedTypes(Class[_])]]
69
+ * @since 2.13.0
70
+ */
71
+ def clearRegisteredReferencedTypes (): Unit = {
72
+ overrideMap.clear()
73
+ }
74
+
22
75
def setDescriptorCache (cache : LookupCache [ClassKey , BeanDescriptor ]): LookupCache [ClassKey , BeanDescriptor ] = {
23
76
val existingCache = _descriptorCache
24
77
_descriptorCache = cache
@@ -124,7 +177,7 @@ class ScalaAnnotationIntrospectorInstance(config: ScalaModule.Config) extends No
124
177
defaultInstantiator : ValueInstantiator ): ValueInstantiator = {
125
178
if (isMaybeScalaBeanType(beanDesc.getBeanClass)) {
126
179
_descriptorFor(beanDesc.getBeanClass).map { descriptor =>
127
- if (descriptor.properties.exists(_.param.exists(_.defaultValue.isDefined))) {
180
+ if (overrideMap.contains(beanDesc.getBeanClass) || descriptor.properties.exists(_.param.exists(_.defaultValue.isDefined))) {
128
181
defaultInstantiator match {
129
182
case std : StdValueInstantiator =>
130
183
new ScalaValueInstantiator (config, std, deserializationConfig, descriptor)
@@ -194,6 +247,55 @@ class ScalaAnnotationIntrospectorInstance(config: ScalaModule.Config) extends No
194
247
case am : AnnotatedMember => isMaybeScalaBeanType(am.getDeclaringClass)
195
248
}
196
249
}
250
+
251
+ private class ScalaValueInstantiator (config : ScalaModule .Config , delegate : StdValueInstantiator ,
252
+ deserializationConfig : DeserializationConfig , descriptor : BeanDescriptor )
253
+ extends StdValueInstantiator (delegate) {
254
+
255
+ private val overriddenConstructorArguments : Array [SettableBeanProperty ] = {
256
+ val overrides = overrideMap.get(descriptor.beanType).map(_.overrides.toMap).getOrElse(Map .empty)
257
+ val applyDefaultValues = deserializationConfig.isEnabled(MapperFeature .APPLY_DEFAULT_VALUES )
258
+ val args = delegate.getFromObjectArguments(deserializationConfig)
259
+ Option (args) match {
260
+ case Some (array) if (applyDefaultValues || overrides.nonEmpty) => {
261
+ array.map {
262
+ case creator : CreatorProperty => {
263
+ // Locate the constructor param that matches it
264
+ descriptor.properties.find(_.param.exists(_.index == creator.getCreatorIndex)) match {
265
+ case Some (pd) => {
266
+ val mappedCreator = overrides.get(pd.name) match {
267
+ case Some (refHolder) => WrappedCreatorProperty (creator, refHolder)
268
+ case _ => creator
269
+ }
270
+ if (applyDefaultValues) {
271
+ pd match {
272
+ case PropertyDescriptor (_, Some (ConstructorParameter (_, _, Some (defaultValue))), _, _, _, _, _) => {
273
+ mappedCreator.withNullProvider(new NullValueProvider {
274
+ override def getNullValue (ctxt : DeserializationContext ): AnyRef = defaultValue()
275
+
276
+ override def getNullAccessPattern : AccessPattern = AccessPattern .DYNAMIC
277
+ })
278
+ }
279
+ case _ => mappedCreator
280
+ }
281
+ } else {
282
+ mappedCreator
283
+ }
284
+ }
285
+ case _ => creator
286
+ }
287
+ }
288
+ }
289
+ }
290
+ case Some (array) => array
291
+ case _ => Array .empty
292
+ }
293
+ }
294
+
295
+ override def getFromObjectArguments (config : DeserializationConfig ): Array [SettableBeanProperty ] = {
296
+ overriddenConstructorArguments
297
+ }
298
+ }
197
299
}
198
300
199
301
trait ScalaAnnotationIntrospectorModule extends JacksonModule {
@@ -213,37 +315,20 @@ object ScalaAnnotationIntrospectorModule extends ScalaAnnotationIntrospectorModu
213
315
214
316
object ScalaAnnotationIntrospector extends ScalaAnnotationIntrospectorInstance (ScalaModule .defaultBuilder)
215
317
216
- private class ScalaValueInstantiator (config : ScalaModule .Config , delegate : StdValueInstantiator ,
217
- deserializationConfig : DeserializationConfig , descriptor : BeanDescriptor )
218
- extends StdValueInstantiator (delegate) {
219
-
220
- private val overriddenConstructorArguments : Array [SettableBeanProperty ] = {
221
- val applyDefaultValues = deserializationConfig.isEnabled(MapperFeature .APPLY_DEFAULT_VALUES ) &&
222
- config.shouldApplyDefaultValuesWhenDeserializing()
223
- val args = delegate.getFromObjectArguments(deserializationConfig)
224
- Option (args) match {
225
- case Some (array) if applyDefaultValues => {
226
- array.map {
227
- case creator : CreatorProperty =>
228
- // Locate the constructor param that matches it
229
- descriptor.properties.find(_.param.exists(_.index == creator.getCreatorIndex)) match {
230
- case Some (PropertyDescriptor (name, Some (ConstructorParameter (_, _, Some (defaultValue))), _, _, _, _, _)) =>
231
- creator.withNullProvider(new NullValueProvider {
232
- override def getNullValue (ctxt : DeserializationContext ): AnyRef = defaultValue()
233
-
234
- override def getNullAccessPattern : AccessPattern = AccessPattern .DYNAMIC
235
- })
236
- case _ => creator
237
- }
238
- case other => other
239
- }
318
+ private case class WrappedCreatorProperty (creatorProperty : CreatorProperty , refHolder : ScalaAnnotationIntrospector .ClassHolder )
319
+ extends CreatorProperty (creatorProperty, creatorProperty.getFullName) {
320
+
321
+ override def getType (): JavaType = {
322
+ super .getType match {
323
+ case rt : ReferenceType if refHolder.valueClass.isDefined =>
324
+ ReferenceType .upgradeFrom(rt, SimpleType .constructUnsafe(refHolder.valueClass.get))
325
+ case ct : CollectionLikeType if refHolder.valueClass.isDefined =>
326
+ CollectionLikeType .upgradeFrom(ct, SimpleType .constructUnsafe(refHolder.valueClass.get))
327
+ case mt : MapLikeType => {
328
+ val valueType = refHolder.valueClass.map(SimpleType .constructUnsafe).getOrElse(mt.getContentType)
329
+ MapLikeType .upgradeFrom(mt, mt.getKeyType, valueType)
240
330
}
241
- case Some (array) => array
242
- case _ => Array .empty
331
+ case other => other
243
332
}
244
333
}
245
-
246
- override def getFromObjectArguments (deserializationConfig : DeserializationConfig ): Array [SettableBeanProperty ] = {
247
- overriddenConstructorArguments
248
- }
249
334
}
0 commit comments