Skip to content

Commit 5b3e78f

Browse files
committed
Upgrade Option Serialization support to use ReferenceType.
1 parent cf5612b commit 5b3e78f

File tree

6 files changed

+218
-165
lines changed

6 files changed

+218
-165
lines changed

build.sbt

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ organization := "com.fasterxml.jackson.module"
77

88
scalaVersion := "2.11.8"
99

10-
crossScalaVersions := Seq("2.10.6", "2.11.8", "2.12.0-M4")
10+
crossScalaVersions := Seq("2.10.6", "2.11.8", "2.12.0-M4)
1111
1212
scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature")
1313

Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
package com.fasterxml.jackson.module.scala.modifiers
22

3+
import java.lang.reflect.Type
4+
5+
import com.fasterxml.jackson.databind.JavaType
6+
import com.fasterxml.jackson.databind.`type`.{ReferenceType, TypeBindings, TypeFactory, TypeModifier}
37
import com.fasterxml.jackson.module.scala.JacksonModule
48

5-
private object OptionTypeModifier extends CollectionLikeTypeModifier {
6-
def BASE = classOf[Option[Any]]
9+
private object OptionTypeModifier extends TypeModifier with GenTypeModifier {
10+
def OPTION = classOf[Option[AnyRef]]
11+
12+
override def modifyType(typ: JavaType, jdkType: Type, context: TypeBindings, typeFactory: TypeFactory): JavaType = {
13+
if (typ.isReferenceType || typ.isContainerType) return typ
14+
15+
if (classObjectFor(jdkType).exists(OPTION.isAssignableFrom)) {
16+
ReferenceType.upgradeFrom(typ, typ.containedTypeOrUnknown(0))
17+
} else typ
18+
}
719
}
820

921
trait OptionTypeModifierModule extends JacksonModule {
1022
this += OptionTypeModifier
11-
}
23+
}

src/main/scala/com/fasterxml/jackson/module/scala/ser/OptionSerializerModule.scala

+161-125
Original file line numberDiff line numberDiff line change
@@ -2,178 +2,214 @@ package com.fasterxml.jackson
22
package module.scala
33
package ser
44

5-
import util.Implicits._
6-
import modifiers.OptionTypeModifierModule
7-
8-
import core.JsonGenerator
9-
import databind._
10-
import jsontype.TypeSerializer
11-
import jsonschema.{JsonSchema, SchemaAware}
12-
import ser.{ContextualSerializer, BeanPropertyWriter, BeanSerializerModifier, Serializers}
13-
import ser.impl.UnknownSerializer
14-
import ser.std.StdSerializer
15-
import com.fasterxml.jackson.databind.`type`.CollectionLikeType
16-
import jsonFormatVisitors.JsonFormatVisitorWrapper
17-
185
import java.lang.reflect.Type
19-
import java.{util => ju}
6+
import com.fasterxml.jackson.annotation.JsonInclude
7+
import com.fasterxml.jackson.core.JsonGenerator
8+
import com.fasterxml.jackson.databind._
9+
import com.fasterxml.jackson.databind.`type`.ReferenceType
10+
import com.fasterxml.jackson.databind.annotation.JsonSerialize
11+
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper
12+
import com.fasterxml.jackson.databind.jsonschema.{JsonSchema, SchemaAware}
13+
import com.fasterxml.jackson.databind.jsontype.TypeSerializer
14+
import com.fasterxml.jackson.databind.ser.impl.{PropertySerializerMap, UnknownSerializer}
15+
import com.fasterxml.jackson.databind.ser.std.StdSerializer
16+
import com.fasterxml.jackson.databind.ser.{ContextualSerializer, Serializers}
17+
import com.fasterxml.jackson.databind.util.NameTransformer
18+
import com.fasterxml.jackson.module.scala.modifiers.OptionTypeModifierModule
19+
import com.fasterxml.jackson.module.scala.util.Implicits._
20+
21+
private object OptionSerializer {
22+
def useStatic(provider: SerializerProvider, property: Option[BeanProperty], referredType: JavaType): Boolean = {
23+
// First: no serializer for `Object.class`, must be dynamic
24+
if (referredType.isJavaLangObject) return false
25+
// but if type is final, might as well fetch
26+
if (referredType.isFinal) return true
27+
// also: if indicated by typing, should be considered static
28+
if (referredType.useStaticType()) return true
29+
// if neither, maybe explicit annotation?
30+
for (
31+
ann <- property.flatMap(p => Option(p.getMember));
32+
intr <- Option(provider.getAnnotationIntrospector)
33+
) {
34+
val typing = intr.findSerializationTyping(ann)
35+
if (typing == JsonSerialize.Typing.STATIC) return true
36+
if (typing == JsonSerialize.Typing.DYNAMIC) return false
37+
}
38+
// and finally, may be forced by global static typing (unlikely...)
39+
provider.isEnabled(MapperFeature.USE_STATIC_TYPING)
40+
}
41+
42+
def findSerializer(provider: SerializerProvider, typ: Class[_], prop: Option[BeanProperty]): JsonSerializer[AnyRef] = {
43+
// Important: ask for TYPED serializer, in case polymorphic handling is needed!
44+
provider.findTypedValueSerializer(typ, true, prop.orNull)
45+
}
46+
47+
def findSerializer(provider: SerializerProvider, typ: JavaType, prop: Option[BeanProperty]): JsonSerializer[AnyRef] = {
48+
// Important: ask for TYPED serializer, in case polymorphic handling is needed!
49+
provider.findTypedValueSerializer(typ, true, prop.orNull)
50+
}
51+
52+
def hasContentTypeAnnotation(provider: SerializerProvider, property: BeanProperty): Boolean = {
53+
val intr = provider.getAnnotationIntrospector
54+
if (property == null || intr == null) return false
55+
intr.refineSerializationType(provider.getConfig, property.getMember, property.getType) != null
56+
}
57+
}
2058

21-
private class OptionSerializer(elementType: Option[JavaType],
59+
private class OptionSerializer(referredType: JavaType,
60+
property: Option[BeanProperty],
2261
valueTypeSerializer: Option[TypeSerializer],
23-
beanProperty: Option[BeanProperty],
24-
elementSerializer: Option[JsonSerializer[AnyRef]])
25-
extends StdSerializer[Option[AnyRef]](classOf[Option[AnyRef]])
26-
with ContextualSerializer
27-
with SchemaAware
28-
{
29-
override def serialize(value: Option[AnyRef], jgen: JsonGenerator, provider: SerializerProvider) {
30-
def doSerialize(v: AnyRef) = {
31-
val cls = v.getClass
32-
val ser = elementType.filter(_.hasGenericTypes) match {
33-
case Some(et) => provider.findValueSerializer(provider.constructSpecializedType(et, cls), beanProperty.orNull)
34-
case None => provider.findValueSerializer(cls, beanProperty.orNull)
35-
}
36-
ser.serialize(v, jgen, provider)
37-
}
62+
valueSerializer: Option[JsonSerializer[AnyRef]],
63+
contentInclusion: Option[JsonInclude.Include],
64+
unwrapper: Option[NameTransformer],
65+
var dynamicSerializers: PropertySerializerMap = PropertySerializerMap.emptyForProperties())
66+
extends StdSerializer[Option[AnyRef]](referredType)
67+
with ContextualSerializer
68+
with SchemaAware {
69+
70+
import OptionSerializer._
71+
72+
override def unwrappingSerializer(transformer: NameTransformer): JsonSerializer[Option[AnyRef]] = {
73+
val ser = valueSerializer.map(_.unwrappingSerializer(transformer))
74+
val unt = unwrapper.map(NameTransformer.chainedTransformer(transformer, _)).getOrElse(transformer)
75+
withResolved(property, valueTypeSerializer, ser, Option(unt), contentInclusion)
76+
}
3877

39-
valueTypeSerializer match {
40-
case Some(vt) => serializeWithType(value, jgen, provider, vt)
41-
case None =>
42-
(value, elementSerializer) match {
43-
case (Some(v), Some(vs: UnknownSerializer)) => doSerialize(v)
44-
case (Some(v), None) => doSerialize(v)
45-
case (Some(v), Some(vs)) => vs.serialize(v, jgen, provider)
46-
case (None, _) => provider.defaultSerializeNull(jgen)
47-
}
48-
}
78+
protected[this] def withResolved(prop: Option[BeanProperty], vts: Option[TypeSerializer],
79+
valueSer: Option[JsonSerializer[AnyRef]], unt: Option[NameTransformer],
80+
contentIncl: Option[JsonInclude.Include]): OptionSerializer = {
81+
if (prop == property && vts == valueTypeSerializer && valueSer == valueSerializer &&
82+
contentIncl == contentInclusion && unt == unwrapper) this
83+
else new OptionSerializer(referredType, prop, vts, valueSer, contentIncl, unt, dynamicSerializers)
4984
}
5085

51-
override def serializeWithType(value: Option[AnyRef], jgen: JsonGenerator, provider: SerializerProvider, typeSer: TypeSerializer) {
52-
def doSerialize(v: AnyRef) = {
53-
val cls = v.getClass
54-
val ser = elementType.filter(_.hasGenericTypes) match {
55-
case Some(et) => provider.findTypedValueSerializer(provider.constructSpecializedType(et, cls), true, beanProperty.orNull)
56-
case None => provider.findTypedValueSerializer(cls, true, beanProperty.orNull)
57-
}
58-
ser.serializeWithType(v, jgen, provider, typeSer)
86+
override def createContextual(prov: SerializerProvider, prop: BeanProperty): JsonSerializer[_] = {
87+
val propOpt = Option(prop)
88+
89+
val vts = valueTypeSerializer.optMap(_.forProperty(prop))
90+
var ser = for (
91+
prop <- propOpt;
92+
member <- Option(prop.getMember);
93+
serDef <- Option(prov.getAnnotationIntrospector.findContentSerializer(member))
94+
) yield prov.serializerInstance(member, serDef)
95+
ser = ser.orElse(valueSerializer).map(prov.handlePrimaryContextualization(_, prop)).asInstanceOf[Option[JsonSerializer[AnyRef]]]
96+
ser = Option(findConvertingContentSerializer(prov, prop, ser.orNull).asInstanceOf[JsonSerializer[AnyRef]])
97+
ser = ser match {
98+
case None => if (hasContentTypeAnnotation(prov, prop)) {
99+
Option(prov.findValueSerializer(referredType, prop)).filterNot(_.isInstanceOf[UnknownSerializer])
100+
} else None
101+
// Why secondary why not primary?
102+
case Some(s) => Option(prov.handleSecondaryContextualization(s, prop).asInstanceOf[JsonSerializer[AnyRef]])
59103
}
60104

61-
(value, elementSerializer) match {
62-
case (Some(v), Some(vs: UnknownSerializer)) => doSerialize(v)
63-
case (Some(v), None) => doSerialize(v)
64-
case (Some(v), Some(vs)) => vs.serializeWithType(v, jgen, provider, typeSer)
65-
case (None, _) => provider.defaultSerializeNull(jgen)
105+
// A few conditions needed to be able to fetch serializer here:
106+
if (ser.isEmpty && useStatic(prov, propOpt, referredType)) {
107+
ser = Option(findSerializer(prov, referredType, propOpt))
66108
}
109+
// Also: may want to have more refined exclusion based on referenced value
110+
val newIncl = propOpt match {
111+
case None => contentInclusion
112+
case Some(p) =>
113+
val pinc = p.findPropertyInclusion(prov.getConfig, classOf[Option[AnyRef]])
114+
val incl = pinc.getContentInclusion
115+
if (incl != JsonInclude.Include.USE_DEFAULTS) {
116+
Some(incl)
117+
} else contentInclusion
118+
}
119+
withResolved(propOpt, vts, ser, unwrapper, newIncl)
120+
}
121+
122+
override def isEmpty(provider: SerializerProvider, value: Option[AnyRef]): Boolean = {
123+
if (value == null || value.isEmpty) return true
124+
if (contentInclusion.isEmpty) return false
125+
val contents = value.get
126+
valueSerializer
127+
.getOrElse(findCachedSerializer(provider, contents.getClass))
128+
.isEmpty(provider, contents)
67129
}
68130

69-
override def createContextual(prov: SerializerProvider, property: BeanProperty): JsonSerializer[_] = {
70-
// Based on the version in AsArraySerializerBase
71-
val typeSer = valueTypeSerializer.optMap(_.forProperty(property))
72-
var ser: Option[JsonSerializer[_]] =
73-
Option(property).flatMap { p =>
74-
Option(p.getMember).flatMap { m =>
75-
Option(prov.getAnnotationIntrospector.findContentSerializer(m)).map { serDef =>
76-
prov.serializerInstance(m, serDef)
77-
}
78-
}
79-
} orElse elementSerializer
80-
ser = Option(findConvertingContentSerializer(prov, property, ser.orNull))
81-
if (ser.isEmpty) {
82-
if (elementType.isDefined) {
83-
if (hasContentTypeAnnotation(prov, property)) {
84-
ser = Option(prov.findValueSerializer(elementType.get, property))
85-
}
131+
override def isUnwrappingSerializer: Boolean = unwrapper.isDefined
132+
133+
override def serialize(opt: Option[AnyRef], gen: JsonGenerator, provider: SerializerProvider): Unit = {
134+
if (opt.isEmpty) {
135+
if (unwrapper.isEmpty) {
136+
provider.defaultSerializeNull(gen)
86137
}
138+
return
87139
}
88-
else {
89-
ser = Option(prov.handleSecondaryContextualization(ser.get, property))
140+
141+
val value = opt.get
142+
val ser = valueSerializer.getOrElse(findCachedSerializer(provider, value.getClass))
143+
valueTypeSerializer match {
144+
case Some(vts) => ser.serializeWithType(value, gen, provider, vts)
145+
case None => ser.serialize(value, gen, provider)
90146
}
91-
if ((ser != elementSerializer) || (property != beanProperty.orNull) || (valueTypeSerializer != typeSer))
92-
new OptionSerializer(elementType, typeSer, Option(property), ser.asInstanceOf[Option[JsonSerializer[AnyRef]]])
93-
else this
94147
}
95148

96-
def hasContentTypeAnnotation(provider: SerializerProvider, property: BeanProperty) = {
97-
Option(property).exists { p =>
98-
Option(provider.getAnnotationIntrospector).exists { intr =>
99-
Option(intr.refineSerializationType(provider.getConfig, p.getMember, p.getType)).isDefined
149+
override def serializeWithType(opt: Option[AnyRef], gen: JsonGenerator, provider: SerializerProvider, typeSer: TypeSerializer): Unit = {
150+
if (opt.isEmpty) {
151+
if (unwrapper.isEmpty) {
152+
provider.defaultSerializeNull(gen)
100153
}
154+
return
101155
}
156+
// Otherwise apply type-prefix/suffix, then std serialize:
157+
typeSer.writeTypePrefixForScalar(opt, gen, classOf[Option[_]])
158+
serialize(opt, gen, provider)
159+
typeSer.writeTypeSuffixForScalar(opt, gen)
102160
}
103161

104-
override def isEmpty(value: Option[AnyRef]): Boolean = value.isEmpty
105-
106162
override def getSchema(provider: SerializerProvider, typeHint: Type): JsonNode =
107163
getSchema(provider, typeHint, isOptional = true)
108164

109165
override def getSchema(provider: SerializerProvider, typeHint: Type, isOptional: Boolean): JsonNode = {
110-
val contentSerializer = elementSerializer.getOrElse {
166+
val contentSerializer = valueSerializer.getOrElse {
111167
val javaType = provider.constructType(typeHint)
112168
val componentType = javaType.getContentType
113-
provider.findTypedValueSerializer(componentType, true, beanProperty.orNull)
169+
provider.findTypedValueSerializer(componentType, true, property.orNull)
114170
}
115171
contentSerializer match {
116172
case cs: SchemaAware => cs.getSchema(provider, contentSerializer.handledType(), isOptional)
117173
case _ => JsonSchema.getDefaultSchemaNode
118174
}
119175
}
120176

121-
override def acceptJsonFormatVisitor(wrapper: JsonFormatVisitorWrapper, javaType: JavaType) {
122-
val containedType = javaType.getContentType
123-
val ser = elementSerializer.getOrElse(wrapper.getProvider.findTypedValueSerializer(containedType, true, beanProperty.orNull))
124-
ser.acceptJsonFormatVisitor(wrapper, containedType)
177+
override def acceptJsonFormatVisitor(visitor: JsonFormatVisitorWrapper, typeHint: JavaType): Unit = {
178+
var ser = valueSerializer.getOrElse(findSerializer(visitor.getProvider, referredType, property))
179+
ser = unwrapper.map(ser.unwrappingSerializer).getOrElse(ser)
180+
ser.acceptJsonFormatVisitor(visitor, referredType)
125181
}
126-
}
127182

128-
private class OptionPropertyWriter(delegate: BeanPropertyWriter) extends BeanPropertyWriter(delegate)
129-
{
130-
override def serializeAsField(bean: AnyRef, jgen: JsonGenerator, prov: SerializerProvider) {
131-
(get(bean), _nullSerializer) match {
132-
// value is None, which we'll serialize as null, but there's no
133-
// null-serializer, which means it should be suppressed
134-
case (None, null) => return
135-
case _ => super.serializeAsField(bean, jgen, prov)
183+
protected[this] def findCachedSerializer(prov: SerializerProvider, typ: Class[_]): JsonSerializer[AnyRef] = {
184+
var ser = dynamicSerializers.serializerFor(typ)
185+
if (ser == null) {
186+
ser = findSerializer(prov, typ, property)
187+
ser = unwrapper.map(ser.unwrappingSerializer).getOrElse(ser)
188+
dynamicSerializers = dynamicSerializers.newWith(typ, ser)
136189
}
137-
}
138-
}
139-
140-
private object OptionBeanSerializerModifier extends BeanSerializerModifier {
141-
142-
override def changeProperties(config: SerializationConfig,
143-
beanDesc: BeanDescription,
144-
beanProperties: ju.List[BeanPropertyWriter]): ju.List[BeanPropertyWriter] = {
145-
import scala.collection.JavaConversions._
146-
beanProperties.transform(w => if (isOption(w)) new OptionPropertyWriter(w) else w)
147-
}
148-
149-
private[this] def isOption(writer: BeanPropertyWriter): Boolean = {
150-
classOf[Option[_]].isAssignableFrom(writer.getPropertyType)
190+
ser
151191
}
152192
}
153193

154194
private object OptionSerializerResolver extends Serializers.Base {
155195

156196
private val OPTION = classOf[Option[_]]
157197

158-
override def findCollectionLikeSerializer(config: SerializationConfig,
159-
`type`: CollectionLikeType,
160-
beanDesc: BeanDescription,
161-
elementTypeSerializer: TypeSerializer ,
162-
elementValueSerializer: JsonSerializer[AnyRef]
163-
): JsonSerializer[_] =
164-
165-
if (!OPTION.isAssignableFrom(`type`.getRawClass)) null
166-
else {
167-
val elementType = `type`.getContentType
168-
val typeSer = Option(elementTypeSerializer).orElse(Option(elementType.getTypeHandler.asInstanceOf[TypeSerializer]))
169-
val valSer = Option(elementValueSerializer).orElse(Option(elementType.getValueHandler.asInstanceOf[JsonSerializer[AnyRef]]))
170-
new OptionSerializer(Option(`type`.getContentType), typeSer, None, valSer)
171-
}
198+
override def findReferenceSerializer(config: SerializationConfig,
199+
refType: ReferenceType,
200+
beanDesc: BeanDescription,
201+
contentTypeSerializer: TypeSerializer,
202+
contentValueSerializer: JsonSerializer[AnyRef]): JsonSerializer[_] = {
203+
if (!OPTION.isAssignableFrom(refType.getRawClass)) return null
204+
new OptionSerializer(refType.getReferencedType, property = None,
205+
valueTypeSerializer = Option(contentTypeSerializer).orElse(Option(refType.getTypeHandler[TypeSerializer])),
206+
valueSerializer = Option(contentValueSerializer).orElse(Option(refType.getValueHandler[JsonSerializer[AnyRef]])),
207+
contentInclusion = None, unwrapper = None)
208+
}
172209
}
173210

174211
trait OptionSerializerModule extends OptionTypeModifierModule {
175212
this += { ctx =>
176213
ctx addSerializers OptionSerializerResolver
177-
ctx addBeanSerializerModifier OptionBeanSerializerModifier
178214
}
179215
}

src/test/scala/com/fasterxml/jackson/module/scala/JacksonTest.scala

-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ package com.fasterxml.jackson.module.scala
33
import com.fasterxml.jackson.databind.{Module, ObjectMapper}
44

55
abstract class JacksonTest extends BaseSpec {
6-
76
def module: Module
87

98
def newMapper = {
109
val result = new ObjectMapper
1110
result.registerModule(module)
1211
result
1312
}
14-
1513
}

0 commit comments

Comments
 (0)