Handle Any data type dynamically in Scala - scala

I have a map in Scala returned by a function which is of type Map[String, Any]
For example:
val map: Map[String, Any] = Map("key1" -> "strVal", "key2" -> List[Map[String, Any]](), "key3" -> Map("k1" -> "v1"))
Now the problem is, to work on the value I get corresponding to a key, I've to use asInstanceOf[] every time. For eg,
val key2Hash = map.getOrElse("key3", Map()).getOrElse("k1", "")
throws error because the Map retrieved is of form Any and I have to use asInstanceOf[] for every situation as belows:
val key2Hash = map.getOrElse("key3", Map()).asInstanceOf[Map[String, String]].getOrElse("k1", "")
Is there a better way to do it? Or should I not be starting of with Map[String, Any] at the first place?

Map[String, Any]? You might as well use python directly!
Joking apart, you can get "nicer" casts syntax using pattern matching:
map.get("key3") match {
case Some(anotherMap: Map[String, String]) => ...
case _ => ...
}

Related

how to access key values within nested map in Scala

I have a List of Maps. One of the maps has another map inside it (2 level deep). I need to access some of keys from the inner most map and finally change the values. The issue I'm facing is to retrieve the keys from the inner most map. I'm very new to Scala and tried different things without luck.
I have flatten the List to Map and then tried to retrieve the key, values. The thing is, I can print the entire inner map, but not sure how to iterate thru that.
Below is the code: at a very basic, I would like to retrieve the values corresponding to keys from innermost map; say for the keys "isStudentLoankRequested", "schoolStructure".
object ListToMaps {
def main(args: Array[String]) {
val dataInputKeysListvar = List(Map("identityKeyName" -> "MY_ID", "identityKeyValue" -> "abcd-1234-xyz"),
Map("identityKeyName" -> "OUR_ID", "identityKeyValue" -> "1234567890",
"identityInformation" -> Map("writeFrequency" -> "NEVER", "studentStatus" -> "SEP", "annualValue" -> 0,
"schoolStructure" -> "OTHER", "studentType" -> "FTS", "occupationDescription" -> "other",
"studentAccountBalanceRange" -> "string", "isStudentLoankRequested" -> "N", "schoolName" -> "myschool",
"parentsIncome" -> 100)),
Map("identityKeyName" -> "FINAL_DECISION_KEY", "identityKeyValue" -> "0000-ABCD-4567-IJKL"))
val x = dataInputKeysListvar.flatten.toMap
val y = x("identityInformation")
if (x.contains("identityInformation")){
println("value of y is" + y)
}
}
}
As you can see from the print stmt, I can print the entire map of the inner most map, but need help in terms of iterating thru that.
If you know at compile time which fields and values you need to change.
You can hard code the logic, like this:
def fixData(data: List[Map[String, Any]]): List[Map[String, Any]] =
data.map { outerMap =>
outerMap.get("identityInformation") match {
case Some(innerMap) =>
// Put as many key pairs you want to change.
// Note: if the key does not exists it will be added!
val updatedInnerMap = innerMap.asInstanceOf[Map[String, String]] ++ Map(
"isStudentLoankRequested" -> "No"
)
outerMap + ("identityInformation" -> updatedInnerMap)
case None =>
outerMap
}
}
If the key-values to change are dynamic, and / or some inner keys may not exists, or if the level of nesting can go on.
You can do something like this:
def updateMap(map: Map[String, Any], updates: Map[String, Any]): Map[String, Any] =
map.map {
case (key, value: Map[_, _]) =>
updates.get(key) match {
case Some(innerUpdates : Map[_, _]) =>
key -> updateMap(
map = value.asInstanceOf[Map[String, Any]],
updates = innerUpdates.asInstanceOf[Map[String, Any]]
)
case Some(newValue) =>
key -> newValue
case None =>
key -> value
}
case (key, value) =>
key -> updates.getOrElse(key, default = value)
}
def fixData(data: List[Map[String, Any]], updates: Map[String, Any]): List[Map[String, Any]] =
data.map(outerMap => updateMap(outerMap, updates))
Note: The above snippets use "unsafe" techniques like asInstanceOf because we lost type safety the moment you got a Map[String, Any]. Always that I see such structure, I think of JSON. I would suggest you to use an appropriate library for managing such kind of data, like circe, instead of writing code as the above.

How to find a map in list of maps where a key has a certain value in Scala?

I have a List[Map[String, Any] and I want to return the map which has a certain value for one of the keys. Here's an obfusticated example:
val items: List[Map[String, Any]] = List(Map("name" -> "A", "size" -> 50), Map("name" -> "B", "size" -> 75), Map("name" -> "C", "size" -> 100)))
val mapB: Map[String, Any] = items.find(|m| m("name") == "B") // Psuedo code
mapB would be Map("name" -> "B", "size" -> 75).
Bonus Question
Is there also a way to return the value of another key instead of the whole Map?
For example, if I only wanted the size value (75) for name B? I know I can extract it from the Map as a two-step operation, but if there's another more idiomatic way, I'd be interested in learning it.
Thanks!
find() returns an Option in case what you're looking for cannot be found.
val mapB: Option[Map[String, Any]] = items.find(_("name") == "B") //safe
or
val mapB: Map[String, Any] = items.find(_("name") == "B").get //unsafe, might throw
To retrieve the size value from the first Map where "name" -> "B" you could do something like this.
val sz :Any = items.find(m => m.get("name").contains("B")
&& m.get("size").nonEmpty).fold(0:Any)(_("size"))
Note that sz is type Any because that's what the type of the Map values. Which means that you'd have to cast it (discouraged) to type Int before you can do anything useful with it.
Avoid type Any. It's a sure sign that you're going in the wrong direction.
Complementing jwvh's answers (and using Tim's suggestion to improve safety).
Here is a function that addresses your bonus question.
def findAndGet(maps: List[Map[String, Any]], condition: (String, Any), key: String): Option[Any] = condition match {
case (k, v) =>
maps.find(map => map.get(k).contains(v)).flatMap(map => map.get(key))
}
findAndGet(maps = items, condition = ("name", "B"), key = "size")
// res0: Option[Any] = Some(75)
BTW, a Map[String, Any] makes me thing you are dealing with JSONs, have you take a look to any of the Scala libraries for dealing with them in a more "typesafe" way?, having an Any in your code should always be an alarm that something is wrong.

scala.Some cannot be cast to scala.collection.immutable.Map Exception

I'm new to scala,
val data = Map("test" -> data1,
"cat" -> None,
"myList" -> Map("test2" -> data2, "test3" -> data3))
val output = data.map(x =>
if (x._2.isInstanceOf[Map[String, Any]]) (x._1 -> Some(x._2))
else x)
Map(test -> data1,
cat -> None,
myList -> Some(Map(test2 -> data2, test3 -> data3)))
val valueToFieldMapping = output(fieldName).get.asInstanceOf[Map[String, String]]
I'm getting
java.lang.ClassCastException: scala.Some cannot be cast to scala.collection.immutable.Map
exception please help me if anyone has an idea about this. Thanks
Firstly, let's clean up the definition of output by using mapValues:
val output = data.mapValues(x =>
if (x.isInstanceOf[Map[String, Any]]) Some(x)
else x)
Then do this
val valueToFieldMapping = output(fieldName).asInstanceOf[Option[Map[String, String]]].get
You can't call get on the Some you generate when creating output because the compile doesn't know it is an Option yet.
Having said all that, the comments are right in saying that using Any and asInstanceOf is really ugly so you need to find a better way of expressing whatever it is you are trying to do. At the very least, use match rather than asInstanceOf so that you can implement the error case if the object is not what you think it is.
The problem is with this line you don't have .get property on instance of Object.
you don't need .get if you want to use .get method then do output.get(fieldName)
val valueToFieldMapping = output(fieldName).get.asInstanceOf[Map[String, String]]
output.get(fieldName) gives you the Option[Object] and you are trying to convert object into the instance of the Map[String, String]
there is no implicit conversion from Option to map so that's the reason you are getting the error:
java.lang.ClassCastException: scala.Some cannot be cast to scala.collection.immutable.Map
or you can do like this:
val valueToFieldMapping: Option[Map[String, String]] = output.get(fieldName).asInstanceOf[Option[Map[String, String]]]

scala: union of two maps whose key type is the same and whose value type is a collection of elements, but whose types are different

I would like to create a union of two maps whose key type is the same and whose value type is a collection of elements, but whose types are different.
Consider the following contrived example:
case class Child(name: String)
val peopleToChildren: Map[String, Seq[Child]] =
Map("max" -> Seq(Child("a"), Child("b")),
"yaneeve" -> Seq(Child("y"), Child("d")))
case class Pet(name: String)
val peopleToPets: Map[String, Seq[Pet]] =
Map("max" -> Seq(Pet("fifi")),
"jill" -> Seq(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")))
val peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = {
// people may have children
// people may have pets
// would like a map from people to a tuple with a potentially empty list of children and a
// potentially empty list of pets
// ???
}
What would be a way to do it which is concise, idiomatic, but still legible?
I found no single function that can do that in the standard scala collections library.
Proposed solutions can be based solely on the standard library, or propose an external solution.
I post it here since I could not easily find an online solution to a seemingly standard operation.
This appears to work.
val peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = {
(peopleToChildren.keySet ++ peopleToPets.keySet).map { k =>
k -> (peopleToChildren.getOrElse(k, Seq())
,peopleToPets.getOrElse(k, Seq()))
}.toMap
}
Get all the keys. For every key do a getOrElse() on each of the feeder Maps.
Just for the curious, here's how it could be done using Scalaz:
import scalaz._, Scalaz._
case class Child(name: String)
val peopleToChildren = Map(
"max" -> List(Child("a"), Child("b")),
"yaneeve" -> List(Child("y"), Child("d"))
)
case class Pet(name: String)
val peopleToPets = Map(
"max" -> List(Pet("fifi")),
"jill" -> List(Pet("bobo"), Pet("jack"), Pet("Roger rabbit"))
)
val peopleToChildrenAndPets: Map[String, (List[Child], List[Pet])] =
peopleToChildren.strengthR(nil[Pet]) |+| peopleToPets.strengthL(nil[Child])
Explanation:
nil[Pet] is just an alias for List.empty[Pet]
strengthR for a given Functor tuples contained values, so that its parameter is at the right. Here it is equivalent to peopleToChildren.mapValues(v => (v, nil[Pet]))
strengthL is the same, but element will be added to the left
|+| is an append operator for a given Semigroup. The one here is derived recursively:
for Map[K, V], it uses |+| to combine values of type V if a given key exists in both Maps. If the value is only present in one of them, it will be retained as is. Here, V = (List[Child], List[Pet])
for tuples (A, B), it again uses |+| to combine both As and Bs. Here, A = List[Child] and B = List[Pet]
for lists of any type (as well as strings, vectors or streams) it does concatenation. This is why I had to change type of Map values to be Lists - for generic Seqs this operation is not defined
Result:
peopleToChildrenAndPets: Map[String, (List[Child], List[Pet])] = Map(
"max" -> (List(Child("a"), Child("b")), List(Pet("fifi"))),
"jill" -> (
List(),
List(Pet("bobo"), Pet("jack"), Pet("Roger rabbit"))
),
"yaneeve" -> (List(Child("y"), Child("d")), List())
)
To answer my own question, the following is the way that I solved it, but it seems overly long and complex:
Welcome to the Ammonite Repl 1.0.2
(Scala 2.11.11 Java 1.8.0_91)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
# case class Child(name: String)
defined class Child
# val peopleToChildren: Map[String, Seq[Child]] =
Map("max" -> Seq(Child("a"), Child("b")),
"yaneeve" -> Seq(Child("y"), Child("d")))
peopleToChildren: Map[String, Seq[Child]] = Map("max" -> List(Child("a"), Child("b")), "yaneeve" -> List(Child("y"), Child("d")))
#
# case class Pet(name: String)
defined class Pet
# val peopleToPets: Map[String, Seq[Pet]] =
Map("max" -> Seq(Pet("fifi")),
"jill" -> Seq(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")))
peopleToPets: Map[String, Seq[Pet]] = Map("max" -> List(Pet("fifi")), "jill" -> List(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")))
#
# val peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = {
// people may have children
// people may have pets
// would like a map from people to a tuple with a potentially empty list of children and a
// potentially empty list of pets
val paddedPeopleToChildren = peopleToChildren.map{ case (person, children) => person -> (children, List.empty[Pet])}
val paddedPeopleToPets = peopleToPets.map{ case (person, pets) => person ->(List.empty[Child], pets)}
val notGoodEnough = paddedPeopleToPets ++ paddedPeopleToChildren // this is here to show that it does not work since it overwrites the value of a key - Map(max -> (List(Child(a), Child(b)),List()), jill -> (List(),List(Pet(bobo), Pet(jack), Pet(Roger rabbit))), yaneeve -> (List(Child(y), Child(d)),List()))
val allSeq = paddedPeopleToPets.toSeq ++ paddedPeopleToChildren.toSeq
val grouped = allSeq.groupBy(_._1).mapValues(_.map { case (_, tup) => tup })
val solution = grouped.mapValues(_.unzip).mapValues {case (wrappedChildren, wrappedPets) => (wrappedChildren.flatten, wrappedPets.flatten)}
solution
}
peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = Map(
"yaneeve" -> (ArrayBuffer(Child("y"), Child("d")), ArrayBuffer()),
"max" -> (ArrayBuffer(Child("a"), Child("b")), ArrayBuffer(Pet("fifi"))),
"jill" -> (ArrayBuffer(), ArrayBuffer(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")))
)

Scala convert Seq[Object] to Map[String, Map[String, String]]

I am new to Scala so I am a bit fighting with maps.
I have
val items = Seq[MyModel]
where MyModel (came from Java) contains getLang, getName and getMessage methods.
Now I need to fill up the
var loadedMessagesMap: Map[String, Map[String, String]] = ListMap.empty
to contain values grouped by lang in structure: lang -> (name -> message). Name property is unique.
Thank you.
Maybe this will help you:
val result: Map[String, Map[String, Seq[String]]] = items.groupBy(_.getLang).map {
case(lang, models) =>
lang -> models.groupBy(_.getName).mapValues(_.map(_.getMessage))
}
It returns a Seq[String] because there might be several messages for the same language and name. Not sure how you want to handle that case.
This should do the trick:
val models: Seq[MyModel] = ???
val mapped = models.map { model =>
model.getLang -> Map(model.getName -> model.getMessage)
}.toMap
I hope this helps you.