3
votes

I understand

  • in Scala use of null should be avoided
  • and Map.get will return a Option[B] and I can use .getOrElse to get the value and fallback to a default value

e.g.

map.getOrElse("key1","default")

Meanwhile I am interacting with a Java library, which some values are null.

e.g. Map("key1"->null)

getOrElse will throw null pointer in this case.

I want to handle both cases and result in writing something like this

  def getOrElseNoNull[A,B](map:Map[A,B],key:A,default:B) = {
    map.get(key) match{
      case Some(x) if x != null => x
      case _ => default
    }
  }

which is quite ugly. (it is Map[Any] and I need a string from that key)

getOrElseNoNull(map,"key1","").asInstanceOf[String])

is it possible to use implicit to extend the map, or any other elegant way?

5

5 Answers

8
votes

If you're dealing with an immutable Map, the safest thing to do would be to filter out all the null values up front (this incurs the creation of yet another Map instance, but unless you have a specific reason to care about performance here it should not be an issue).

val withoutNulls = map.filter{case (k,v)=> v != null}

Any key that was holding a null is gone, and as such getOrElse on this key will return None. Trivial, and does the job once for all.

5
votes

Implicit extension classes to the rescue:

implicit class NullOccludingMap[K, V](private val underlying: Map[K, V]) extends AnyVal {
  def getNonNullOrElse(key: K, default: V): V = {
    underlying.get(key) match {
      case Some(value) if value != null => value
      case _ => default
    }
  }
}

Then you can use it anywhere it is in scope:

val test = Map("x" -> "Hi", "y" -> null)
test.getNonNullOrElse("z", "") // ""
test.getNonNullOrElse("y", "") // ""
1
votes

One possibility is to map the values over Option():

val withoutNulls: Map[Int, Option[String]] = withNulls.mapValues(Option.apply)

This gives you the possibility to handle missing values and nulls the same way:

val nullsEqualMissing: Map[Int, Option[String]] = withoutNulls.withDefaultValue(None)
nullsEqualMissing(1).fold{ "nullOrMissing" }{ identity }

Or to handle missing values separately:

withoutNulls.get(1).fold{ "missing" }{ _.fold{ "null" }{ identity }}
0
votes

There is no real need to create something new, scala supports several methods to achieve this with default values:

// define a function to handle special values
Map("1" -> "2").withDefault( _ => "3").apply("4")

// default values for all unknown values
Map("1" -> "2").withDefaultValue("3").apply("4")

// handle a specific case
Map("1" -> "2").getOrElse("unknown", "3")

Another option is to use the Option to get the null value in a less ugly way:

None.orNull

This will get you the Option value or return null in case of None.

0
votes

Another simple solution is to wrap map.get result with Scala Option.

 val value = Option(map.get(key))