Collections in scala, how to get elements in map - scala

From lift-json:
scala> val json = parse("""
{
"name": "joe",
"addresses": {
"address1": {
"street": "Bulevard",
"city": "Helsinki"
},
"address2": {
"street": "Soho",
"city": "London"
}
}
}""")
scala> case class Address(street:String, city: String)
scala> case class PersonWithAddresses(name: String, addresses: Map[String, Address])
scala> val joe = json.extract[PersonWithAddresses]
res0: PersonWithAddresses("joe", Map("address1" -> Address("Bulevard", "Helsinki"),
"address2" -> Address("Soho", "London")))
I want to access elements of joe. I want to know the Joe's address1 city for example. How?
Bonus Question:
what if PersonWithAddresses was
case class PersonWithAddress(name:String, addresses: Map[String, List[Address]])
how would I extract the size of that list?
P.S. question:
what's the difference between joe.addresses("address1").size() and
joe.addresses.get("address1").size ?

Your question has nothing really to do with json and lift itself. You already have your object, you just don't know how to use scala collections.
In case without list, you can get your city with:
# joe.addresses("address1")
res4: Address = Address("Bulevard", "Helsinki")
# res4.city
res5: String = "Helsinki"
or joe.addresses("address1").city for short.
In case of list
case class PersonWithAddress(name:String, addresses: Map[String, List[Address]])
you just call size on list.
joe.addresses("address1").size
As for a difference between these two:
# res7.addresses("address1").size
res8: Int = 1
# res7.addresses.get("address1").size
res9: Int = 1
There is a big difference, see what happens when you call get
# res7.addresses.get("address1")
res10: Option[List[Address]] = Some(List(Address("Bulevard", "Helsinki")))
It returns an Option which could be viewed as a collection of size 0 or 1. Checking its size is not what you want to do.
map.get("key")
returns an Option which is either Some(value) if value is present in map, or None if it's not
map("key") or desugared map.apply("key") returns the item associated with key or exception if element is not present in the map.

Related

How to convert List[String] to List[Object] in Scala

I need to change from one List[String] to List[MyObject] in scala.
For example,JSON input is like below
employee: {
name: "test",
employeeBranch: ["CSE", "IT", "ECE"]
}
Output should be like this,
Employee: {
Name: "test",
EmployeeBranch:[{"branch": "CSE"}, {"branch": "IT"}, {"branch": "ECE"}]
}
Input case class:
Class Office(
name: Option[String],
employeeBranch: Option[List[String]])
Output case class:
Class Output(
Name: Option[String],
EmployeeBranch: Option[List[Branch]])
case class Branch(
branch: Option[String])
This is the requirement.
It is hard to answer without knowing details of the particular JSON library, but an Object is probably represented as a Map. So to convert a List[String] to a List[Map[String, String]] you can do this:
val list = List("CSE", "IT", "ECE")
val map = list.map(x => Map("branch" -> x))
This gives
List(Map(branch -> CSE), Map(branch -> IT), Map(branch -> ECE))
which should convert to the JSON you want.

Fetch all entities from case class and convert them to string

I have a case class like below:
case class Class1(field1: String,
field2: Option[String] = None,
var var1: Option[String] = None,
var var2: Option[Boolean] = None,
var var3: Option[Double] = None
)
The list of variables is a bit longer. Now I want to convert all variables, which are inside the class, into a string. Say Option[] must be omitted and also Boolean, Double and Number must be converted to string type. My first approach was:
def anyOptionalToString(class1Dataset: Dataset[Class1]): DataFrame = {
val ds1 = class1Dataset.map { class1 =>
(
class1.field1,
class1.field2.getOrElse(""),
class1.var1.getOrElse(""),
class1.var2.getOrElse(false),
class1.var3.getOrElse(-1.0)
)
}
Is there a way to cast them without calling every field?
Speak in a kind of loop or something similar?
What I would do, is creating a new Seq containing the defaults you want to have. Let's say:
val defaults = Seq("", "", "", false, -1)
Now, we can use the productIterator to iterate over the existing elements, and choose whether we want to use the existing value, or the default:
val c1 = Class1("f1", Some("f2"), None, Some(true), Some(3))
c1.productIterator.zip(defaults.iterator).map {
case (None, default) => default
case (Some(value), _) => value
case (value, _) => value
}.map(_.toString)
The resulting type of the code above is Iterator[String]. Code run can be found at Scastie.

replace list element with another and return the new list

I am kind of stuck with this, and I know this is a bloody simple question :(
I have a case class such as:
case class Students(firstName: String, lastName: String, hobby: String)
I need to return a new list but change the value of hobby based on Student name. For example:
val classToday = List(Students("John","Smith","Nothing"))
Say if student name is John I want to change the hobby to Soccer so the resulting list should be:
List(Students("John","Smith","Soccer")
I think this can be done via map? I have tried:
classToday.map(x => if (x.firstName == "John") "Soccer" else x)
This will just replace firstName with Soccer which I do not want, I tried setting the "True" condition to x.hobby == "Soccer" but that does not work.
I think there is a simple solution to this :(
The lambda function in map has to return a Students value again, not just "Soccer". For example, if you had to replace everyone's hobbies with "Soccer", this is not right:
classToday.map(x => "Soccer")
What you want is the copy function:
classToday.map(x => x.copy(hobby = "Soccer"))
Or for the original task:
classToday.map(x => if (x.firstName == "John") x.copy(hobby = "Soccer") else x)
You can use pattern-matching syntax to pretty up this type of transition.
val newList = classToday.map{
case s#Students("John",_,_) => s.copy(hobby = "Soccer")
case s => s
}
I suggest to make it more generic, you can create a map of names to hobbies:
For example:
val firstNameToHobby = Map("John" -> "Soccer", "Brad" -> "Basketball")
And use it as follows:
case class Students(firstName: String, lastName: String, hobby: String)
val classToday = List(Students("John","Smith","Nothing"), Students("Brad","Smith","Nothing"))
val result = classToday.map(student => student.copy(hobby = firstNameToHobby.getOrElse(student.firstName, "Nothing")))
// result = List(Students(John,Smith,Soccer), Students(Brad,Smith,Basketball))
It would be better if you can create a mapping between the firstName of the student with Hobby, then you can use it like this:
scala> val hobbies = Map("John" -> "Soccer", "Messi" -> "Soccer", "Williams" -> "Cricket")
hobbies: scala.collection.immutable.Map[String,String] = Map(John -> Soccer, Messi -> Soccer, Williams -> Cricket)
scala> case class Student(firstName: String, lastName: String, hobby: String)
defined class Student
scala> val students = List(Student("John", "Smith", "Nothing"), Student("Williams", "Lopez", "Nothing"), Student("Thomas", "Anderson", "Nothing"))
students: List[Student] = List(Student(John,Smith,Nothing), Student(Williams,Lopez,Nothing), Student(Thomas,Anderson,Nothing))
scala> students.map(student => student.copy(hobby = hobbies.getOrElse(student.firstName, "Nothing")))
res2: List[Student] = List(Student(John,Smith,Soccer), Student(Williams,Lopez,Cricket), Student(Thomas,Anderson,Nothing))

scala parse json objects in order

I have this following json input where I am trying to parse the name field in-order
scala> result
res6: play.api.libs.json.JsValue = {"L0":
{"name":"FASHION","id":"50000"},"L1":{"name":"ACCESSORIES AND TRAVEL","id":"51000"},"L2":{"name":"FASHION ACCESSORIES","id":"51001"},"L3":{"name":"MENS FASHION ACCESSORIES","id":"51100"},"L4":{"name":"MENS HATS","id":"51204"}}
scala> result \\ "name"
res5: Seq[play.api.libs.json.JsValue] = List("ACCESSORIES AND TRAVEL", "MENS HATS", "MENS FASHION ACCESSORIES", "FASHION ACCESSORIES", "FASHION")
What I am trying is to get those names in-order like
List("FASHION", "ACCESSORIES AND TRAVEL", "FASHION ACCESSORIES", "MENS FASHION ACCESSORIES", "MENS HATS")
Is there a way to achieve that with play Json library?
With Play JSON I always use case classes. So your example would look like:
import play.api.libs.json._
val json = """{"L0":
{"name":"FASHION","id":"50000"},"L1":{"name":"ACCESSORIES AND TRAVEL","id":"51000"},"L2":{"name":"FASHION ACCESSORIES","id":"51001"},"L3":{"name":"MENS FASHION ACCESSORIES","id":"51100"},"L4":{"name":"MENS HATS","id":"51204"}}
"""
case class Element(id: String, name: String)
object Element {
implicit val jsonFormat: Format[Element] = Json.format[Element]
}
Json.parse(json).validate[Map[String, Element]] match {
case JsSuccess(elems, _) => println(elems.toList.sortBy(_._1).map(e => e._2.name))
case other => println(s"Handle exception $other")
}
What this gives you, is that you can sort the result by the key - the info that is lost in your solution.

How to map based on multiple lists with arbitrary elements?

I have the following model:
case class Car(brand: String, year: Int, model: String, ownerId: String)
case class Person(firstName: String, lastName: String, id: String)
case class House(address: String, size: Int, ownerId: String)
case class Info(id: String, lastName: String, carModel: String, address: String)
I want to build a List[Info] based on the following lists:
val personL: List[Person] = List(Person("John", "Doe", "1"), Person("Jane", "Doe", "2"))
val carL: List[Car] = List(Car("Mercedes", 1999, "G", "1"), Car("Tesla", 2016, "S", "4"), Car("VW", 2015, "Golf", "2"))
val houseL: List[House] = List(House("Str. 1", 1000, "2"), House("Bvl. 3", 150, "8"))
The info should be gathered based on the personL, for example:
val info = personL.map { p =>
val car = carL.find(_.ownerId.equals(p.id))
val house = houseL.find(_.ownerId.equals(p.id))
val carModel = car.map(_.model)
val address = house.map(_.address)
Info(p.id, p.lastName, carModel.getOrElse(""), address.getOrElse(""))
}
Result:
info: List[Info] = List(Info(1,Doe,G,), Info(2,Doe,Golf,Str. 1))
Now I am wondering if there's an expression which is more concise than my map construct which solves exactly my problem.
Here is one option by building the maps from ownerid to model and address firstly, and then look up the info while looping through the person List:
val carMap = carL.map(car => car.ownerId -> car.model).toMap
// carMap: scala.collection.immutable.Map[String,String] = Map(1 -> G, 4 -> S, 2 -> Golf)
val addrMap = houseL.map(house => house.ownerId -> house.address).toMap
// addrMap: scala.collection.immutable.Map[String,String] = Map(2 -> Str. 1, 8 -> Bvl. 3)
personL.map(p => Info(p.id, p.lastName, carMap.getOrElse(p.id, ""), addrMap.getOrElse(p.id, "")))
// res3: List[Info] = List(Info(1,Doe,G,), Info(2,Doe,Golf,Str. 1))
I would say use for comprehensions. If you need exactly that result which in that case would resemble a left join then the for comprehension is still ugly:
for {
person <- persons
model <- cars.find(_.ownerId == person.id).map(_.model).orElse(Some("")).toList
address <- houses.find(_.ownerId == person.id).map(_.address).orElse(Some("")).toList
} yield Info(person.id, person.lastName, model, address)
Note that you can remove the .toList call in this exceptional case as the two Option generators appear after the collection generators.
If you can sacrifice the default model / address values then it looks simple enough:
for {
person <- persons
car <- cars if car.ownerId == person.id
house <- houses if house.ownerId == person.id
} yield Info(person.id, person.lastName, car.model, car.address)
Hope that helps.
May be converting the individual lists in hashmaps with a map function and look up by key instead of iterating all those lists for every element of person might help?