Circe: force an optional field to `null` - scala

Is there a way to serialize a single None field to "null" ?
For example:
// When None, I'd like to serialize only f2 to `null`
case class Example(f1: Option[Int], f2: Option[Int])
val printer = Printer.noSpaces.copy(dropNullValues = true)
Example(None, None).asJson.pretty(printer) === """{"f2":null}"""

You can do this pretty straightforwardly by mapping a filter over the output of an encoder (which can be derived, defined with Encoder.forProductN, etc.):
import io.circe.{ Json, ObjectEncoder }
import io.circe.generic.semiauto.deriveEncoder
case class Example(f1: Option[Int], f2: Option[Int])
val keepSomeNulls: ((String, Json)) => Boolean = {
case ("f1", v) => !v.isNull
case (_, _) => true
}
implicit val encodeExample: ObjectEncoder[Example] =
deriveEncoder[Example].mapJsonObject(_.filter(keepSomeNulls))
And then:
scala> import io.circe.syntax._
import io.circe.syntax._
scala> Example(Some(1), Some(2)).asJson.noSpaces
res0: String = {"f1":1,"f2":2}
scala> Example(Some(1), None).asJson.noSpaces
res1: String = {"f1":1,"f2":null}
scala> Example(None, Some(2)).asJson.noSpaces
res2: String = {"f2":2}
scala> Example(None, None).asJson.noSpaces
res3: String = {"f2":null}
Note that configuring a printer to drop null values will still remove "f2": null here. This is part of the reason I think in general it's best to make the preservation of null values solely the responsibility of the printer, but in a case like this, the presence or absence of null-valued fields is clearly semantically meaningful, so you kind of have to mix up the levels.

Related

Why does scala allow concating Strings with Option[Strings](or any other type)?

I mistakenly concatted a string with an Option[String] while coding in scala.
I expected as a strongly typed language, scala would not allow me to do such operation.
This is what I tried.
This works
scala> val a:String = "aaa"
val a: String = aaa
scala> val b:Option[String] = Some("bbbb")
val b: Option[String] = Some(bbbb)
scala> a + b
val res0: String = aaaSome(bbbb)
scala> val c:Option[String] = None
val c: Option[String] = None
scala> val d = a + c
val d: String = aaaNone
scala> val e = 1
val e: Int = 1
scala> a + e
val res2: String = aaa1
while this does not work
scala> val f:Option[String] = Some("ffff")
val f: Option[String] = Some(ffff)
scala> val g:Option[String] = None
val g: Option[String] = None
scala> f + g
^
error: type mismatch;
found : Option[String]
required: String
Why does scala allow such behavior? Dynamically typed languages like python will stop me from adding strings to int types, None types or any type other than strings. Curious if this design is intentional? If so why?
Scala contains an implicit class any2stringadd in it's Predef package. This class is responsible for these concatenation operations.
implicit final class any2stringadd[A](private val self: A) extends AnyVal {
def +(other: String): String = String.valueOf(self) + other
}
What it means is that, by default, scope contains a method + which can concatenate value of any type A with string by converting value of this type to string via String.valueOf(...).
I can't speak of design choices, I agree that this might be an unexpected behavior. The same applies to Scala's native == method. For example, this code compiles just ok: Some("a") == "b". This can lead to nasty bugs in filtering and other methods.
If you want to eliminate this behavior I suggest you take a look at https://typelevel.org/cats/ library, which introduces different typeclasses that can solve this problem.
For example, for string concatenation you can use Semigroup typeclass (which has tons of other useful use-cases as well):
import cats.Semigroup
Semigroup[String].combine("a", "b") // works, returns "ab"
Semigroup[String].combine("a", Some("b")) // won't work, compilation error
This looks tedious, but there is a syntactic sugar:
import cats.implicits._
"a" |+| "b" // works, returns "ab"
"a" |+| Some("b") // won't work, compilation error
// |+| here is the same as Semigroup[String].combine
The same thing applies to == method. Instead you can use Eq typeclass:
import cats.implicits._
"a" == Some("b") // works, no error, but could be unexpected
"a" === Some("b") // compilation error (Cats Eq)
"a" === "b" // works, as expected
"a" =!= "b" // same as != but type safe

Scala - Get string representation of object property name, not value, for comparison

I want to be able to get the string representation of an objects property name, not the properties value, so that I can compare it with a variables value inside a conditional statement.
case class CustomObj(name: T)
case class PropertyObj(property: String)
val custObj = CustomObj("Chris")
val propObj = PropertyObj("name")
if(propObj.property.equals(custObj. /* the property name as a String, so "name", not the value ("Chris"*/)) {
// do something
}
How can I access what is essentially the key of the property on the CustomObj?
Try productElementNames like so
case class CustomObj(name: String)
case class PropertyObj(property: String)
val custObj = CustomObj("Chris")
val propObj = PropertyObj("name")
if (custObj.productElementNames.toList.headOption.contains(propObj.property)) { ... } else { ... }
Addressing the comment, based on Krzysztof, try shapeless solution
import shapeless._
import shapeless.ops.record._
def firstPropertyNameOf[P <: Product, L <: HList](p: P)(implicit
gen: LabelledGeneric.Aux[P, L],
toMap: ToMap[L]): Option[String] = {
toMap(gen.to(p)).map{ case (k: Symbol, _) => k.name }.toList.headOption
}
firstPropertyNameOf(custObj).contains(propObj.property) // res1: Boolean = true
I will assume you don't know the type of custObj at compile time. Then you'll have to use runtime reflection in Scala 2.12.
scala> case class CustomObj(name: String)
defined class CustomObj
scala> val custObj: Any = CustomObj("Chris")
custObj: Any = CustomObj(Chris)
scala> import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.currentMirror
scala> val sym = currentMirror.classSymbol(custObj.getClass)
sym: reflect.runtime.universe.ClassSymbol = class CustomObj
scala> val props = sym.info.members.collect{ case m if m.isMethod && m.asMethod.isCaseAccessor => m.name.toString }
props: Iterable[String] = List(name)
scala> if (props.exists(_ == "name")) println("ok")
ok

Check case class with Option fields to make sure all of them are None using Shapeless HList

I have a case class (simplified):
case class UserData(name: Option[String], age: Option[String]) {
lazy val nonEmpty = name.isDefined || age.isDefined // TODO
}
Can I replace the current implementation of nonEmpty check using, for instance, Shapeless' HList in order to enumerate all the fields to check that all of them are set to None or at least one has a value?
case class UserData(name: Option[String], age: Option[String]) {
lazy val isEmpty = this.productIterator.forall(_ == None)
}
UserData(None,None).isEmpty
UserData(None,Some("s")).isEmpty
I suppose you want to do different behavior inside case class, if you dont then #pamu answer is what you are looking for.
If you really want to use shapeless you can, but no need.
I think you also check with pure scala using productIterator.
scala> val data = UserData(None, None)
data: UserData = UserData(None,None)
scala> data.productIterator.forall {
| case x: Option[_] => x.isDefined
| case _ => false
| }
res2: Boolean = false
scala> val data = UserData(Some("foo"), Some("bar"))
data: UserData = UserData(Some(foo),Some(bar))
scala> data.productIterator.forall {
| case x: Option[_] => x.isDefined
| case _ => false // you may throw exception if you are not expecting this case
| }
res3: Boolean = true

Json4s: Trouble while trying to convert Json attribute to java.sql.Date

I am using Json4s to deserialize json messages. I have a case class like
case class A(id: Int, b: Option[java.sql.Date])
Whenever I try to convert a json message to case class A, I get none as the value of b
scala> read[A]("""{"id":1,"b":12345}""")
res2: A = A(1,None)
scala> read[A]("""{"id":1,"b":"12345"}""")
res3: A = A(1,None)
scala> read[A]("""{"id":1,"b":"12/12/2014"}""")
res4: A = A(1,None)
How can I fix this issue
Something along these lines (you might want to be more specific with formats). And then mix this trait in the classes which need to have access to this custom serializer.
import org.json4s.DefaultJsonFormats._
trait JsonFormats {
case object DateSerializer extends CustomSerializer[java.sql.Date](format => (
{
case JString(s) => Date.valueOf(s)
case JNull => null
},
{
case d: Date => JString(d.toString())
}
)
)
implicit val json4sFormats = native.Serialization.formats(NoTypeHints) + DateSerializer
}

Scala: convert string to Int or None

I am trying to get a number out of an xml field
...
<Quantity>12</Quantity>
...
via
Some((recipe \ "Main" \ "Quantity").text.toInt)
Sometimes there may not be a value in the xml, though. The text will be "" and this throws an java.lang.NumberFormatException.
What is a clean way to get either an Int or a None?
scala> import scala.util.Try
import scala.util.Try
scala> def tryToInt( s: String ) = Try(s.toInt).toOption
tryToInt: (s: String)Option[Int]
scala> tryToInt("123")
res0: Option[Int] = Some(123)
scala> tryToInt("")
res1: Option[Int] = None
Scala 2.13 introduced String::toIntOption:
"5".toIntOption // Option[Int] = Some(5)
"abc".toIntOption // Option[Int] = None
"abc".toIntOption.getOrElse(-1) // Int = -1
More of a side note on usage following accepted answer. After import scala.util.Try, consider
implicit class RichOptionConvert(val s: String) extends AnyVal {
def toOptInt() = Try (s.toInt) toOption
}
or similarly but in a bit more elaborated form that addresses only the relevant exception in converting onto integral values, after import java.lang.NumberFormatException,
implicit class RichOptionConvert(val s: String) extends AnyVal {
def toOptInt() =
try {
Some(s.toInt)
} catch {
case e: NumberFormatException => None
}
}
Thus,
"123".toOptInt
res: Option[Int] = Some(123)
Array(4,5,6).mkString.toOptInt
res: Option[Int] = Some(456)
"nan".toInt
res: Option[Int] = None
Here's another way of doing this that doesn't require writing your own function and which can also be used to lift to Either.
scala> import util.control.Exception._
import util.control.Exception._
scala> allCatch.opt { "42".toInt }
res0: Option[Int] = Some(42)
scala> allCatch.opt { "answer".toInt }
res1: Option[Int] = None
scala> allCatch.either { "42".toInt }
res3: scala.util.Either[Throwable,Int] = Right(42)
(A nice blog post on the subject.)