Skip to content
Draft
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ out/
*.iml
.idea
.DS_Store
/.bsp
6 changes: 6 additions & 0 deletions upickle/core/src/upickle/core/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ trait Config {
*/
def optionsAsNulls: Boolean = true

/**
* When `optionsAsNull`s is enabled, Whether top-level `None`s are serialized as
* `null` or are omitted
*/
def serializeNones: Boolean = true

/**
* Configure whether you want upickle to skip unknown keys during de-serialization
* of `case class`es. Can be overriden for the entire serializer via `override def`, and
Expand Down
44 changes: 38 additions & 6 deletions upickle/implicits/src-2/upickle/implicits/internal/Macros2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,13 @@ object Macros2 {
yield q"this.storeValueIfNotFound($i, ${defaultValues(i).get})"
}

if (!${c.prefix}.serializeNones) {
..${
for(i <- types.indices if types(i).typeSymbol.fullName == "scala.Option")
yield q"this.storeValueIfNotFound($i, None)"
}
}

// Special-case 64 because java bit shifting ignores any RHS values above 63
// https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.19
if (${
Expand Down Expand Up @@ -564,9 +571,22 @@ object Macros2 {
$select
)
"""
val default = if (defaultValue.isEmpty) snippet
else q"""if (${serDfltVals(symbol)} || $select != ${defaultValue.get}) $snippet"""
default :: Nil

val isOption = tpeOfField.typeSymbol.fullName == "scala.Option"
val hasDefault = defaultValue.nonEmpty

def nonesCond = q"${c.prefix}.serializeNones || $select.nonEmpty"
def defaultsCond = q"${serDfltVals(symbol)} || $select != ${defaultValue.get}"

val res = if (isOption && hasDefault) {
q"""if (($nonesCond) && ($defaultsCond)) $snippet"""
} else if(hasDefault) {
q"""if ($defaultsCond) $snippet"""
} else if(isOption) {
q"""if ($nonesCond) $snippet"""
} else snippet

res :: Nil
}
}
}
Expand All @@ -586,9 +606,21 @@ object Macros2 {
}
else fail(s"Invalid type for flattening: $tpeOfField.")
case None =>
val snippet = if (defaultValue.isEmpty) q"1"
else q"""if (${serDfltVals(symbol)} || $select != ${defaultValue.get}) 1 else 0"""
snippet :: Nil
val isOption = tpeOfField.typeSymbol.fullName == "scala.Option"
val hasDefault = defaultValue.nonEmpty

def nonesCond = q"${c.prefix}.serializeNones || $select.nonEmpty"
def defaultsCond = q"${serDfltVals(symbol)} || $select != ${defaultValue.get}"

val res = if (isOption && hasDefault) {
q"""if (($nonesCond) && ($defaultsCond)) 1 else 0"""
} else if(hasDefault) {
q"""if ($defaultsCond) 1 else 0"""
} else if(isOption) {
q"""if ($nonesCond) 1 else 0"""
} else q"1"

res :: Nil
}
}
}
Expand Down
47 changes: 35 additions & 12 deletions upickle/implicits/src-3/upickle/implicits/macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,14 @@ private def storeDefaultsImpl[T](x: Expr[upickle.implicits.BaseCaseObjectContext
val statements = allFields[T]
.filter(!_._5)
.zipWithIndex
.map { case ((_, _, _, default, _), i) =>
.map { case ((field, _, _, default, _), i) =>
default match {
case Some(defaultValue) => '{${x}.storeValueIfNotFound(${Expr(i)}, ${defaultValue})}
case None => '{}
case None =>
field.typeRef.asType match {
case '[Option[?]] => '{${x}.storeValueIfNotFound(${Expr(i)}, None)}
case _ => '{}
}
}
}

Expand Down Expand Up @@ -239,12 +243,22 @@ private def writeLengthImpl[T](thisOuter: Expr[upickle.core.Types with upickle.i
report.error(s"${typeSymbol} is not a case class or a Iterable[(String, _)]")
Nil
}
}
else if (!defaults.contains(label)) List('{1})
else {
val serDflt = serDfltVals(thisOuter, field, classTypeRepr.typeSymbol)
} else {
val isOption = field.typeRef.asType match {
case '[Option[?]] => true
case _ => false
}
val hasDefault = defaults.contains(label)
def defaultsCond = {
val serDflt = serDfltVals(thisOuter, field, classTypeRepr.typeSymbol)
'{ ${serDflt} || ${select.asExprOf[Any]} != ${defaults(label)} }
}
def nonesCond = '{ ${ thisOuter }.serializeNones || ${ select.asExprOf[Option[?]] }.nonEmpty }
List(
'{if (${serDflt} || ${select.asExprOf[Any]} != ${defaults(label)}) 1 else 0}
if (hasDefault && isOption) '{if ($defaultsCond && $nonesCond) 1 else 0}
else if (hasDefault) '{if ($defaultsCond) 1 else 0}
else if (isOption) '{if ($nonesCond) 1 else 0}
else '{1}
)
}

Expand Down Expand Up @@ -330,12 +344,21 @@ private def writeSnippetsImpl[R, T, W[_]](thisOuter: Expr[upickle.core.Types wit
${select.asExprOf[Any]},
)
}
val isOption = field.typeRef.asType match {
case '[Option[?]] => true
case _ => false
}
val hasDefault = defaults.contains(label)
def nonesCond = '{ ${ thisOuter }.serializeNones || ${select.asExprOf[Option[?]]}.nonEmpty }
def defaultsCond = {
val serDflt = serDfltVals(thisOuter, field, classTypeRepr.typeSymbol)
'{ ${serDflt} || ${select.asExprOf[Any]} != ${defaults(label)} }
}
List(
if (!defaults.contains(label)) snippet
else {
val serDflt = serDfltVals(thisOuter, field, classTypeRepr.typeSymbol)
'{if ($serDflt || ${select.asExprOf[Any]} != ${defaults(label)}) $snippet}
}
if (hasDefault && isOption) '{ if ($defaultsCond && $nonesCond) $snippet }
else if (hasDefault) '{ if ($defaultsCond) $snippet }
else if (isOption) '{ if ($nonesCond) $snippet }
else snippet
)
}

Expand Down
29 changes: 29 additions & 0 deletions upickle/test/src/upickle/example/ExampleTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ object Defaults{
implicit val rw: RW[FooDefault] = macroRW
}
}
object Nones{
case class OptionWrapper(opt: Option[String])
object OptionWrapper{
implicit val rw: RW[OptionWrapper] = macroRW
}
}
object Keyed{
case class KeyBar(@upickle.implicits.key("hehehe") kekeke: Int)
object KeyBar{
Expand Down Expand Up @@ -105,6 +111,7 @@ import Sealed._
import Simple._
import Recursive._
import Defaults._
import Nones._

object ExampleTests extends TestSuite {

Expand Down Expand Up @@ -417,6 +424,28 @@ object ExampleTests extends TestSuite {
SerializeDefaults.write(FooDefault(i = 10, s = "lol")) ==> """{"i":10,"s":"lol"}"""
SerializeDefaults.write(FooDefault()) ==> """{"i":10,"s":"lol"}"""
}
test("serializeNones = false"){
object SerializeNones extends upickle.AttributeTagged{
override def serializeNones = false
}
implicit val optionWrapperRW: SerializeNones.ReadWriter[OptionWrapper] = SerializeNones.macroRW
SerializeNones.write(OptionWrapper(None)) ==> "{}"
SerializeNones.write(OptionWrapper(opt = Some("lol"))) ==> """{"opt":"lol"}"""

SerializeNones.read[OptionWrapper]("{}") ==> OptionWrapper(None)
SerializeNones.read[OptionWrapper]("""{"opt":"lol"}""") ==> OptionWrapper(opt = Some("lol"))
}
test("serializeNones = true"){
object SerializeNones extends upickle.AttributeTagged{
override def serializeNones = true
}
implicit val optionWrapperRW: SerializeNones.ReadWriter[OptionWrapper] = SerializeNones.macroRW
SerializeNones.write(OptionWrapper(None)) ==> """{"opt":null}"""
SerializeNones.write(OptionWrapper(opt = Some("lol"))) ==> """{"opt":"lol"}"""

SerializeNones.read[OptionWrapper]("""{"opt":null}""") ==> OptionWrapper(None)
SerializeNones.read[OptionWrapper]("""{"opt":"lol"}""") ==> OptionWrapper(opt = Some("lol"))
}
}

test("transform"){
Expand Down
Loading