Skip to content
This repository was archived by the owner on Nov 27, 2018. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 44 additions & 7 deletions src/main/scala/jacks/case.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer

import java.lang.reflect.{Constructor, Method}

class CaseClassSerializer(t: JavaType, accessors: Array[Accessor]) extends StdSerializer[Product](t) {
class CaseClassSerializer(t: JavaType, accessors: Array[Accessor], skipNulls: Boolean) extends StdSerializer[Product](t) {
override def serialize(value: Product, g: JsonGenerator, p: SerializerProvider) {
g.writeStartObject()

Expand All @@ -28,19 +28,34 @@ class CaseClassSerializer(t: JavaType, accessors: Array[Accessor]) extends StdSe
}

@inline final def include(a: Accessor, s: JsonSerializer[AnyRef], v: AnyRef): Boolean = a.include match {
case ALWAYS => true
case NON_DEFAULT => default(a) != v
case NON_EMPTY => !s.isEmpty(v)
case NON_NULL => v != null
case Some(ALWAYS) => true
case Some(NON_DEFAULT) => default(a) != v
case Some(NON_EMPTY) => !s.isEmpty(v)
case Some(NON_NULL) => v != null
case None => {
// unfortunately, while Jackson serializers have an isEmpty() to match the getEmptyValue() of deserializers,
// they lack an isNull() to match the getNullValue() of deserializers.
// We cannot use isEmpty, as otherwise empty iterables would also match. Therefore, the only
// viable solution here is to match Option's None explicitly, unfortunately.
//
// null or None should /not/ be skipped it there is an explicit default, unless the default
// happens to be null or None. But do not skip other values just because they match the default.
!skipNulls || (v != null && v != None) || (!hasNoDefault(a) && {
val df=default(a)
(df != null && df != None)
})
}
}

@inline final def default(a: Accessor) = a.default match {
case Some(m) => m.invoke(null)
case None => null
}

@inline final def hasNoDefault(a: Accessor) = a.default == None
}

class CaseClassDeserializer(t: JavaType, c: Creator) extends JsonDeserializer[Any] {
class CaseClassDeserializer(t: JavaType, c: Creator, checkNulls: Boolean) extends JsonDeserializer[Any] {
val fields = c.accessors.map(a => a.name -> None).toMap[String, Option[Object]]
val types = c.accessors.map(a => a.name -> a.`type`).toMap

Expand Down Expand Up @@ -70,7 +85,28 @@ class CaseClassDeserializer(t: JavaType, c: Creator) extends JsonDeserializer[An
val params = c.accessors.map { a =>
values(a.name) match {
case Some(v) => v
case None => c.default(a)
case None => {
if (checkNulls) {
// refuse to store nulls into case class fields, unless
// explicitly requested with a default value
// In case of Option, a missing property becomes None

if (c.hasNoDefault(a)) {
val d = ctx.findContextualValueDeserializer(a.`type`, null)
val e = d.getNullValue
if (e != null) e else
throw ctx.mappingException("Required property '"+a.name+"' is missing.")
} else {
// c hasDefault(a), hence return the default
c.default(a)
}
} else {
// Jacks 2.1.4 behavior will use c.default().
// default() should use d.getNullValue, but instead
// always returns null (even for Option)
c.default(a)
}
}
}
}

Expand All @@ -82,6 +118,7 @@ trait Creator {
val accessors: Array[Accessor]
def apply(args: Seq[AnyRef]): Any
def default(a: Accessor): AnyRef
def hasNoDefault(a: Accessor) = a.default == None
}

class ConstructorCreator(c: Constructor[_], val accessors: Array[Accessor]) extends Creator {
Expand Down
8 changes: 5 additions & 3 deletions src/main/scala/jacks/jacks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import scala.collection.JavaConversions.JConcurrentMapWrapper
import java.io._
import java.util.concurrent.ConcurrentHashMap

trait JacksMapper {
class JacksMapper private (options:JacksOptions) {
val mapper = new ObjectMapper
mapper.registerModule(new ScalaModule)
mapper.registerModule(new ScalaModule(options))

def readValue[T: Manifest](src: Array[Byte]): T = mapper.readValue(src, resolve)
def readValue[T: Manifest](src: InputStream): T = mapper.readValue(src, resolve)
Expand All @@ -37,4 +37,6 @@ trait JacksMapper {
})
}

object JacksMapper extends JacksMapper
object JacksMapper extends JacksMapper(JacksOptions.defaults) {
def withOptions(opts:JacksOption*) = new JacksMapper(JacksOptions(opts:_*))
}
20 changes: 20 additions & 0 deletions src/main/scala/jacks/jacksOptions.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (C) 2011 - Will Glozer. All rights reserved.

package com.lambdaworks.jacks

sealed abstract class JacksOption
object JacksOption {
case class CaseClassCheckNulls(enabled:Boolean) extends JacksOption
case class CaseClassSkipNulls(enabled:Boolean) extends JacksOption
}


private[jacks] class JacksOptions(opts:Seq[JacksOption]=Seq.empty) {
def caseClassCheckNulls=opts contains JacksOption.CaseClassCheckNulls(true)
def caseClassSkipNulls =opts contains JacksOption.CaseClassSkipNulls(true)
}
private[jacks] object JacksOptions {
def apply(opts:JacksOption*) =
new JacksOptions(opts.groupBy(_.getClass()).toSeq.map(_._2.last))
def defaults=new JacksOptions()
}
26 changes: 14 additions & 12 deletions src/main/scala/jacks/module.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ import java.lang.reflect.{Constructor, Method}

import tools.scalap.scalax.rules.scalasig.ScalaSig

class ScalaModule extends Module {
class ScalaModule(options:JacksOptions) extends Module {
def version = new Version(2, 1, 0, null, "com.lambdaworks", "jacks")
def getModuleName = "ScalaModule"

def setupModule(ctx: Module.SetupContext) {
ctx.addSerializers(new ScalaSerializers)
ctx.addDeserializers(new ScalaDeserializers)
ctx.addSerializers(new ScalaSerializers(options))
ctx.addDeserializers(new ScalaDeserializers(options))
}
}

class ScalaDeserializers extends Deserializers.Base {
class ScalaDeserializers(options:JacksOptions) extends Deserializers.Base {
override def findBeanDeserializer(t: JavaType, cfg: DeserializationConfig, bd: BeanDescription): JsonDeserializer[_] = {
val cls = t.getRawClass

Expand Down Expand Up @@ -76,8 +76,9 @@ class ScalaDeserializers extends Deserializers.Base {
new TupleDeserializer(t)
} else if (classOf[Product].isAssignableFrom(cls)) {
ScalaTypeSig(cfg.getTypeFactory, t) match {
case Some(sts) if sts.isCaseClass => new CaseClassDeserializer(t, sts.creator)
case _ => null
case Some(sts) if sts.isCaseClass =>
new CaseClassDeserializer(t, sts.creator, options.caseClassCheckNulls)
case _ => null
}
} else if (classOf[Symbol].isAssignableFrom(cls)) {
new SymbolDeserializer
Expand Down Expand Up @@ -116,7 +117,7 @@ class ScalaDeserializers extends Deserializers.Base {
}
}

class ScalaSerializers extends Serializers.Base {
class ScalaSerializers(options:JacksOptions) extends Serializers.Base {
override def findSerializer(cfg: SerializationConfig, t: JavaType, bd: BeanDescription): JsonSerializer[_] = {
val cls = t.getRawClass

Expand All @@ -132,8 +133,9 @@ class ScalaSerializers extends Serializers.Base {
new TupleSerializer(t)
} else if (classOf[Product].isAssignableFrom(cls)) {
ScalaTypeSig(cfg.getTypeFactory, t) match {
case Some(sts) if sts.isCaseClass => new CaseClassSerializer(t, sts.annotatedAccessors)
case _ => null
case Some(sts) if sts.isCaseClass =>
new CaseClassSerializer(t, sts.annotatedAccessors, options.caseClassSkipNulls)
case _ => null
}
} else if (classOf[Symbol].isAssignableFrom(cls)) {
new SymbolSerializer(t)
Expand All @@ -150,7 +152,7 @@ case class Accessor(
`type`: JavaType,
default: Option[Method],
ignored: Boolean = false,
include: Include = ALWAYS
include: Option[Include] = None
)

class ScalaTypeSig(val tf: TypeFactory, val `type`: JavaType, val sig: ScalaSig) {
Expand Down Expand Up @@ -199,7 +201,7 @@ class ScalaTypeSig(val tf: TypeFactory, val `type`: JavaType, val sig: ScalaSig)
annotations.foldLeft(accessor) {
case (accessor, a:JsonProperty) if a.value != "" => accessor.copy(name = a.value)
case (accessor, a:JsonIgnore) => accessor.copy(ignored = a.value)
case (accessor, a:JsonInclude) => accessor.copy(include = a.value)
case (accessor, a:JsonInclude) => accessor.copy(include = Some(a.value))
case (accessor, _) => accessor
}
}.toArray
Expand All @@ -211,7 +213,7 @@ class ScalaTypeSig(val tf: TypeFactory, val `type`: JavaType, val sig: ScalaSig)
val ignored = ignore.value.toSet
accessors.map(a => a.copy(ignored = ignored.contains(a.name)))
case (accessors, include: JsonInclude) =>
accessors.map(a => a.copy(include = include.value))
accessors.map(a => a.copy(include = Some(include.value)))
case (accessors, _) =>
accessors
}
Expand Down