1
1
package com .fasterxml .jackson .module .scala .introspect
2
2
3
- import java .lang .annotation .Annotation
4
3
import com .fasterxml .jackson .annotation .JsonCreator
5
- import com .fasterxml .jackson .databind .`type` .ClassKey
4
+ import com .fasterxml .jackson .databind .`type` .{ ClassKey , CollectionLikeType , MapLikeType , ReferenceType , SimpleType }
6
5
import com .fasterxml .jackson .databind .cfg .MapperConfig
7
6
import com .fasterxml .jackson .databind .deser .std .StdValueInstantiator
8
7
import com .fasterxml .jackson .databind .deser ._
9
8
import com .fasterxml .jackson .databind .introspect ._
10
9
import com .fasterxml .jackson .databind .util .{AccessPattern , LRUMap , LookupCache }
11
- import com .fasterxml .jackson .databind .{BeanDescription , DeserializationConfig , DeserializationContext , DeserializationFeature , MapperFeature }
10
+ import com .fasterxml .jackson .databind .{BeanDescription , DeserializationConfig , DeserializationContext , JavaType , MapperFeature }
12
11
import com .fasterxml .jackson .module .scala .JacksonModule
13
12
import com .fasterxml .jackson .module .scala .util .Implicits ._
14
13
14
+ import java .lang .annotation .Annotation
15
+
15
16
object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueInstantiators {
16
- private [this ] var _descriptorCache : LookupCache [ClassKey , BeanDescriptor ] =
17
+ private [this ] var _descriptorCache : LookupCache [ClassKey , BeanDescriptor ] =
17
18
new LRUMap [ClassKey , BeanDescriptor ](16 , 100 )
18
19
20
+ case class ClassHolder (valueClass : Option [Class [_]] = None )
21
+ private case class ClassOverrides (overrides : scala.collection.mutable.Map [String , ClassHolder ] = scala.collection.mutable.Map .empty)
22
+
23
+ private val overrideMap = scala.collection.mutable.Map [Class [_], ClassOverrides ]()
24
+
25
+ /**
26
+ * jackson-module-scala does not always properly handle deserialization of Options or Collections wrapping
27
+ * Scala primitives (eg Int, Long, Boolean). There are general issues with serializing and deserializing
28
+ * Scala 2 Enumerations. This function will not help with Enumerations.
29
+ * <p>
30
+ * This function is experimental and may be removed or significantly reworked in a later release.
31
+ * <p>
32
+ * These issues can be worked around by adding Jackson annotations on the affected fields.
33
+ * This function is designed to be used when it is not possible to apply Jackson annotations.
34
+ *
35
+ * @param clazz the (case) class
36
+ * @param fieldName the field name in the (case) class
37
+ * @param referencedType the referenced type of the field - for `Option[Long]` - the referenced type is `Long`
38
+ * @see [[clearRegisteredReferencedTypes() ]]
39
+ * @see [[clearRegisteredReferencedTypes(Class[_])]]
40
+ * @since 2.13.0
41
+ */
42
+ def registerReferencedValueType (clazz : Class [_], fieldName : String , referencedType : Class [_]): Unit = {
43
+ val overrides = overrideMap.getOrElseUpdate(clazz, ClassOverrides ()).overrides
44
+ overrides.get(fieldName) match {
45
+ case Some (holder) => overrides.put(fieldName, holder.copy(valueClass = Some (referencedType)))
46
+ case _ => overrides.put(fieldName, ClassHolder (valueClass = Some (referencedType)))
47
+ }
48
+ }
49
+
50
+ /**
51
+ * clears the state associated with reference types for the given class
52
+ *
53
+ * @param clazz the class for which to remove the registered reference types
54
+ * @see [[registerReferencedValueType ]]
55
+ * @see [[clearRegisteredReferencedTypes() ]]
56
+ * @since 2.13.0
57
+ */
58
+ def clearRegisteredReferencedTypes (clazz : Class [_]): Unit = {
59
+ overrideMap.remove(clazz)
60
+ }
61
+
62
+ /**
63
+ * clears all the state associated with reference types
64
+ *
65
+ * @see [[registerReferencedValueType ]]
66
+ * @see [[clearRegisteredReferencedTypes(Class[_])]]
67
+ * @since 2.13.0
68
+ */
69
+ def clearRegisteredReferencedTypes (): Unit = {
70
+ overrideMap.clear()
71
+ }
72
+
19
73
def setDescriptorCache (cache : LookupCache [ClassKey , BeanDescriptor ]): LookupCache [ClassKey , BeanDescriptor ] = {
20
74
val existingCache = _descriptorCache
21
75
_descriptorCache = cache
@@ -106,22 +160,38 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI
106
160
extends StdValueInstantiator (delegate) {
107
161
108
162
private val overriddenConstructorArguments : Array [SettableBeanProperty ] = {
163
+ val overrides = overrideMap.get(descriptor.beanType).map(_.overrides.toMap).getOrElse(Map .empty)
109
164
val applyDefaultValues = config.isEnabled(MapperFeature .APPLY_DEFAULT_VALUES )
110
165
val args = delegate.getFromObjectArguments(config)
111
166
Option (args) match {
112
- case Some (array) if applyDefaultValues => {
167
+ case Some (array) if ( applyDefaultValues || overrides.nonEmpty) => {
113
168
array.map {
114
- case creator : CreatorProperty =>
169
+ case creator : CreatorProperty => {
115
170
// Locate the constructor param that matches it
116
171
descriptor.properties.find(_.param.exists(_.index == creator.getCreatorIndex)) match {
117
- case Some (PropertyDescriptor (name, Some (ConstructorParameter (_, _, Some (defaultValue))), _, _, _, _, _)) =>
118
- creator.withNullProvider(new NullValueProvider {
119
- override def getNullValue (ctxt : DeserializationContext ): AnyRef = defaultValue()
120
- override def getNullAccessPattern : AccessPattern = AccessPattern .DYNAMIC
121
- })
172
+ case Some (pd) => {
173
+ val mappedCreator = overrides.get(pd.name) match {
174
+ case Some (refHolder) => WrappedCreatorProperty (creator, refHolder)
175
+ case _ => creator
176
+ }
177
+ if (applyDefaultValues) {
178
+ pd match {
179
+ case PropertyDescriptor (_, Some (ConstructorParameter (_, _, Some (defaultValue))), _, _, _, _, _) => {
180
+ mappedCreator.withNullProvider(new NullValueProvider {
181
+ override def getNullValue (ctxt : DeserializationContext ): AnyRef = defaultValue()
182
+
183
+ override def getNullAccessPattern : AccessPattern = AccessPattern .DYNAMIC
184
+ })
185
+ }
186
+ case _ => mappedCreator
187
+ }
188
+ } else {
189
+ mappedCreator
190
+ }
191
+ }
122
192
case _ => creator
123
193
}
124
- case other => other
194
+ }
125
195
}
126
196
}
127
197
case Some (array) => array
@@ -140,7 +210,7 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI
140
210
if (isMaybeScalaBeanType(beanDesc.getBeanClass)) {
141
211
142
212
_descriptorFor(beanDesc.getBeanClass).map { descriptor =>
143
- if (descriptor.properties.exists(_.param.exists(_.defaultValue.isDefined))) {
213
+ if (overrideMap.contains(beanDesc.getBeanClass) || descriptor.properties.exists(_.param.exists(_.defaultValue.isDefined))) {
144
214
defaultInstantiator match {
145
215
case std : StdValueInstantiator =>
146
216
new ScalaValueInstantiator (std, config, descriptor)
@@ -213,3 +283,21 @@ trait ScalaAnnotationIntrospectorModule extends JacksonModule {
213
283
this += { _.appendAnnotationIntrospector(ScalaAnnotationIntrospector ) }
214
284
this += { _.addValueInstantiators(ScalaAnnotationIntrospector ) }
215
285
}
286
+
287
+ private case class WrappedCreatorProperty (creatorProperty : CreatorProperty , refHolder : ScalaAnnotationIntrospector .ClassHolder )
288
+ extends CreatorProperty (creatorProperty, creatorProperty.getFullName) {
289
+
290
+ override def getType (): JavaType = {
291
+ super .getType match {
292
+ case rt : ReferenceType if refHolder.valueClass.isDefined =>
293
+ ReferenceType .upgradeFrom(rt, SimpleType .constructUnsafe(refHolder.valueClass.get))
294
+ case ct : CollectionLikeType if refHolder.valueClass.isDefined =>
295
+ CollectionLikeType .upgradeFrom(ct, SimpleType .constructUnsafe(refHolder.valueClass.get))
296
+ case mt : MapLikeType => {
297
+ val valueType = refHolder.valueClass.map(SimpleType .constructUnsafe).getOrElse(mt.getContentType)
298
+ MapLikeType .upgradeFrom(mt, mt.getKeyType, valueType)
299
+ }
300
+ case other => other
301
+ }
302
+ }
303
+ }
0 commit comments