Related
Type signature of groupBy is (in scala language, but really language-agnostic):
def groupBy[K](f: A => K): Map[K, Seq[A]]
I implemented a groupBy that returns multiple K, so that each A can be put into multiple groups at the same time. Something like this:
def multiGroupBy[K](f: A => Seq[K]): Map[K, Seq[A]]
I do something like:
case class Animal(name: String, traits: Seq[String])
List(
Animal("cat", Seq("nocturnal", "feline")),
Animal("dog", Seq("canine")),
Animal("wolf", Seq("nocturnal", "canine"))
).multiGroupBy(animal => animal.traits)
// Map(nocturnal -> List(cat, wolf), feline -> List(cat), canine -> List(dog, wolf))
Name multiGroupBy works but I'm wondering if there is already a term (perhaps in haskell world?) for operation like above.
If you have Scalaz dependency, you can do it with a foldMap.
import scalaz._
import Scalaz._
case class Animal(name: String, traits: Seq[String])
val animals = List(
Animal("cat", Seq("nocturnal", "feline")),
Animal("dog", Seq("canine")),
Animal("wolf", Seq("nocturnal", "canine"))
)
val r1 = animals.foldMap(a => a.traits.map(t => t -> List(a)).toMap)
println(r1)
// Map(nocturnal -> List(Animal(cat,List(nocturnal, feline)), Animal(wolf,List(nocturnal, canine))), feline -> List(Animal(cat,List(nocturnal, feline))), canine -> List(Animal(dog,List(canine)), Animal(wolf,List(nocturnal, canine))))
val r2 = animals.foldMap(a => a.traits.map(t => t -> List(a.name)).toMap)
println(r2)
// Map(nocturnal -> List(cat, wolf), feline -> List(cat), canine -> List(dog, wolf))
What we did here is that we created a Map[String, List[Animal]] for each animal in animals list and let Monoid[Map[String, List[Animal]]] to merge each maps.
For example Animal("cat", Seq("nocturnal", "feline")) turned into Map("nocturnal" -> List(Animal("cat", Seq("nocturnal", "feline"))), "feline" -> List(Animal("cat", Seq("nocturnal", "feline")))).
For furhter reading about monoid: http://eed3si9n.com/learning-scalaz/Monoid.html
I have a lot of client code that build Map using the same keys (to query MongoDB).
My idea is to provide helper methods that hide keys.
First try, I have used default parameters (cf object Builder below) but the client hava to deal with Option
I now use a builder pattern (cf class Builder below)
Is there a better way ?
class Builder {
val m = collection.mutable.Map[String, Int]()
def withA(a: Int) = {m += (("a", a))}
def withB(b: Int) = {m += (("b", b))}
def withC(c: Int) = {m += (("c", c))}
def build = m.toMap
}
object Builder {
def build1(a: Option[Int] = None, b: Option[Int] = None, c: Option[Int] = None): Map[String, Int] = {
val optPairs = List(a.map("a" -> _),
b.map("b" -> _),
c.map("c" -> _))
val pairs = optPairs.flatten
Map(pairs: _*)
}
}
object Client {
def main(args: Array[String]) {
println(Builder.build1(b = Some(2)))
println(new Builder().withB(2))
}
}
An easy solution to avoid having to deal with options when calling Builder.build1 is to define an implicit conversion to automatically wrap any value into an Some:
implicit def wrap[T]( x: T ) = Some( x )
And boom, you can omit the wrapping and directly do:
scala> Builder.build1( a = 123, c = 456 )
res1: Map[String,Int] = Map(a -> 123, c -> 456)
However, this is pretty dangerous given that options are pervasive and you don't want to pull such a general converion into scope.
To fix this you can define your own "option" class that you'll use just for the purpose of defining those optional parameters:
abstract sealed class OptionalArg[+T] {
def toOption: Option[T]
}
object OptionalArg{
implicit def autoWrap[T]( value: T ): OptionalArg[T] = SomeArg(value)
implicit def toOption[T]( arg: OptionalArg[T] ): Option[T] = arg.toOption
}
case class SomeArg[+T]( value: T ) extends OptionalArg[T] {
def toOption = Some( value )
}
case object NoArg extends OptionalArg[Nothing] {
val toOption = None
}
You can then redefine Build.build1 as:
def build1(a: OptionalArg[Int] = NoArg, b: OptionalArg[Int] = NoArg, c: OptionalArg[Int] = NoArg): Map[String, Int]
And then once again, you can directly call Build.build1 without explicitely wrapping the argument with Some:
scala> Builder.build1( a = 123, c = 456 )
res1: Map[String,Int] = Map(a -> 123, c -> 456)
With the notable difference that now we are not pulling anymore a dangerously broad conversion into cope.
UPDATE: In response to the comment below "to go further in my need, arg can be a single value or a list, and I have awful Some(List(sth)) in my client code today"
You can add another conversion to wrap individual parameters into one element list:
implicit def autoWrapAsList[T]( value: T ): OptionalArg[List[T]] = SomeArg(List(value))
Then say that your method expects an optional list like this:
def build1(a: OptionalArg[List[Int]] = NoArg, b: OptionalArg[Int] = NoArg, c: OptionalArg[Int] = NoArg): Map[String, Int] = {
val optPairs = List(a.map("a" -> _.sum),
b.map("b" -> _),
c.map("c" -> _))
val pairs = optPairs.flatten
Map(pairs: _*)
}
You can now either pass an individual element or a list (or just like before, no argument at all):
scala> Builder.build1( a = 123, c = 456 )
res6: Map[String,Int] = Map(a -> 123, c -> 456)
scala> Builder.build1( a = List(1,2,3), c = 456 )
res7: Map[String,Int] = Map(a -> 6, c -> 456)
scala> Builder.build1( c = 456 )
res8: Map[String,Int] = Map(c -> 456)
One last warning: even though we have defined our very own "option" class, it is still true that you should always use implicit conversions with some care,
so take some time to balance whether the convenience is worth the risk in your use case.
When I execute in the REPL, it works (due to implicit mapWrites in the scope):
scala> Map("a"->1l, "b"->2l)
res0: scala.collection.immutable.Map[String,Long] = Map(a -> 1, b -> 2)
scala> Map("c" -> res0, "d" -> res0)
res1: scala.collection.immutable.Map[String,scala.collection.immutable.Map[String,Long]] = Map(c -> Map(a -> 1, b -> 2), d -> Map(a -> 1, b -> 2))
scala> import play.api.libs.json._
import play.api.libs.json._
scala> Json.toJson(res1)
res2: play.api.libs.json.JsValue = {"c":{"a":1,"b":2},"d":{"a":1,"b":2}}
Why my code still doesn't compile (it's the same type as in the REPL) ?
No Json deserializer found for type Map[String,Map[String,Long]]. Try to implement an implicit Writes or Format for this type.
[edit] I've found a workaround but i don't understand why i need it :
implicit def mapWrites = Writes[Map[String,Map[String,Long]]] ( m => Writes.mapWrites(Writes.mapWrites[Long]).writes(m))
Play 2.1 JSON API does not provide a serializer for the Type Map[String, Object].
Define case class and Format for the specific type instead of Map[String, Object]:
// { "val1" : "xxx", "val2" : ["a", "b", "c"] }
case class Hoge(val1: String, val2: List[String])
implicit val hogeFormat = Json.format[Hoge]
If you don't want to create case class.
The following code provides JSON serializer/deserializer for Map[String, Object]:
implicit val objectMapFormat = new Format[Map[String, Object]] {
def writes(map: Map[String, Object]): JsValue =
Json.obj(
"val1" -> map("val1").asInstanceOf[String],
"val2" -> map("val2").asInstanceOf[List[String]]
)
def reads(jv: JsValue): JsResult[Map[String, Object]] =
JsSuccess(Map("val1" -> (jv \ "val1").as[String], "val2" -> (jv \ "val2").as[List[String]]))
}
Jackson doesn't know how to deserialize the Scala collection class scala.collection.immutable.Map, because it doesn't implement any of the Java collection interfaces.
You can either deserialize to a Java collection:
val mapData = mapper.readValue(jsonContent, classOf[java.util.Map[String,String]])
or add the Scala module to the mapper:
mapper.registerModule(DefaultScalaModule)
val mapData = mapper.readValue(jsonContent, classOf[Map[String,String]])
or you can also try like this
import play.api.libs.json._
val a1=Map("val1"->"a", "val2"->"b")
Json.toJSon(a1)
Because a1 is just Map[String,String] that works OK.
But if I have something more complex like where I have Map[String,Object], that doesn't work:
val a = Map("val1" -> "xxx", "val2"-> List("a", "b", "c"))
Json.toJSon(a1)
>>> error: No Json deserializer found for type scala.collection.immutable.Map[String,Object]
I found that I can do something like the following:
val a2 = Map("val1" -> Json.toJson("a"), "val2" -> Json.toJson(List("a", "b", "c")))
Json.toJson(a2)
And that works.`
I have two nested case classes:
case class InnerClass(param1: String, param2: String)
case class OuterClass(myInt: Int, myInner: InnerClass)
val x = OuterClass(11, InnerClass("hello", "world"))
Which I want to convert to nested Maps of type Map[String,Any] so that I get something like this:
Map(myInt -> 11, myInner -> Map(param1 -> hello, param2 -> world))
Of course, the solution should be generic and work for any case class.
Note:
This discussion gave a good answer on how to map a single case class to a Map. But I couldn't adapt it to nested case classes. Instead I get:
Map(myInt -> 11, myInner -> InnerClass(hello,world)
As Luigi Plinge notes in a comment above, this is a very bad idea—you're throwing type safety out the window and will be stuck with a lot of ugly casts and runtime errors.
That said, it's pretty easy to do what you want with the new Scala 2.10 Reflection API:
def anyToMap[A: scala.reflect.runtime.universe.TypeTag](a: A) = {
import scala.reflect.runtime.universe._
val mirror = runtimeMirror(a.getClass.getClassLoader)
def a2m(x: Any, t: Type): Any = {
val xm = mirror reflect x
val members = t.declarations.collect {
case acc: MethodSymbol if acc.isCaseAccessor =>
acc.name.decoded -> a2m((xm reflectMethod acc)(), acc.typeSignature)
}.toMap
if (members.isEmpty) x else members
}
a2m(a, typeOf[A])
}
And then:
scala> println(anyToMap(x))
Map(myInt -> 11, myInner -> Map(param1 -> hello, param2 -> world))
Do not do this, though. In fact you should do your absolute best to avoid runtime reflection altogether in Scala—it's really almost never necessary. I'm only posting this answer because if you do decide that you must use runtime reflection, you're better off using the Scala Reflection API than Java's.
Just call it recursively. So
def getCCParams(cc: AnyRef) =
(Map[String, Any]() /: cc.getClass.getDeclaredFields) {(a, f) =>
f.setAccessible(true)
val value = f.get(cc) match {
// this covers tuples as well as case classes, so there may be a more specific way
case caseClassInstance: Product => getCCParams(caseClassInstance)
case x => x
}
a + (f.getName -> value)
}
Here is a more principled solution based on shapeless.
https://github.com/yongjiaw/datacrafts
class NoSchemaTest extends FlatSpec with ShapelessProduct.Implicits {
"Marshalling and unmarshalling with Map" should "be successful" in {
val op = NoSchema.of[TestClass]
assert(
op.operator.marshal(
Map(
"v1" -> 10,
"v5" -> Map("_2" -> 12),
"v3" -> Iterable(Seq("v21" -> 3)),
"v6" -> TestClass3(v31 = 5)
)) == TestClass(
v1 = 10,
v5 = (null, 12),
v3 = Some(Seq(Some(
TestClass2(
v21 = 3,
v22 = null
)))),
v6 = Some(TestClass3(v31 = 5)),
v2 = None,
v4 = null
)
)
assert(
op.operator.unmarshal(
TestClass(
v1 = 1,
v2 = null
)
) == Map(
"v1" -> 1,
"v2" -> null,
// the rest are default values
"v6" -> null,
"v5" -> Map("_2" -> 2, "_1" -> "a"),
"v4" -> null,
"v3" -> Seq(
Map(
"v21" -> 3,
"v22" -> Map("v" -> Map(), "v32" -> Seq(12.0), "v31" -> 0)
)
)
)
)
}
}
object NoSchemaTest {
case class TestClass(v1: Int,
v2: Option[Seq[Option[Double]]] = None,
v3: Option[Seq[Option[TestClass2]]] = Some(Seq(Some(TestClass2()))),
v4: Seq[Int] = null,
v5: (String, Int) = ("a", 2),
v6: Option[TestClass3] = None
)
case class TestClass2(v21: Int = 3,
v22: TestClass3 = TestClass3(0)
)
case class TestClass3(v31: Int,
v32: Iterable[Double] = Seq(12),
v: Map[String, Int] = Map.empty
)
}
trait DefaultRule extends Operation.Rule {
override def getOperator[V](operation: Operation[V]): Operation.Operator[V] = {
operation.context.noSchema match {
case _: Primitive[V] => new PrimitiveOperator[V](operation)
case shapeless: ShapelessProduct[V, _] =>
new ShapelessProductMapper[V](operation, shapeless)
case option: OptionContainer[_] =>
new OptionOperator[option.Elem](
option.element, operation.asInstanceOf[Operation[Option[option.Elem]]])
.asInstanceOf[Operation.Operator[V]]
case map: MapContainer[_] =>
new MapOperator[map.Elem](
map.element, operation.asInstanceOf[Operation[Map[String, map.Elem]]])
.asInstanceOf[Operation.Operator[V]]
case seq: SeqContainer[_] =>
new SeqOperator[seq.Elem](
seq.element, operation.asInstanceOf[Operation[Seq[seq.Elem]]])
.asInstanceOf[Operation.Operator[V]]
case iterable: IterableContainer[_] =>
new IterableOperator[iterable.Elem](
iterable.element, operation.asInstanceOf[Operation[Iterable[iterable.Elem]]])
.asInstanceOf[Operation.Operator[V]]
}}}
Is there a nice way I can convert a Scala case class instance, e.g.
case class MyClass(param1: String, param2: String)
val x = MyClass("hello", "world")
into a mapping of some kind, e.g.
getCCParams(x) returns "param1" -> "hello", "param2" -> "world"
Which works for any case class, not just predefined ones. I've found you can pull the case class name out by writing a method that interrogates the underlying Product class, e.g.
def getCCName(caseobj: Product) = caseobj.productPrefix
getCCName(x) returns "MyClass"
So I'm looking for a similar solution but for the case class fields. I'd imagine a solution might have to use Java reflection, but I'd hate to write something that might break in a future release of Scala if the underlying implementation of case classes changes.
Currently I'm working on a Scala server and defining the protocol and all its messages and exceptions using case classes, as they are such a beautiful, concise construct for this. But I then need to translate them into a Java map to send over the messaging layer for any client implementation to use. My current implementation just defines a translation for each case class separately, but it would be nice to find a generalised solution.
This should work:
def getCCParams(cc: AnyRef) =
cc.getClass.getDeclaredFields.foldLeft(Map.empty[String, Any]) { (a, f) =>
f.setAccessible(true)
a + (f.getName -> f.get(cc))
}
Because case classes extend Product one can simply use .productIterator to get field values:
def getCCParams(cc: Product) = cc.getClass.getDeclaredFields.map( _.getName ) // all field names
.zip( cc.productIterator.to ).toMap // zipped with all values
Or alternatively:
def getCCParams(cc: Product) = {
val values = cc.productIterator
cc.getClass.getDeclaredFields.map( _.getName -> values.next ).toMap
}
One advantage of Product is that you don't need to call setAccessible on the field to read its value. Another is that productIterator doesn't use reflection.
Note that this example works with simple case classes that don't extend other classes and don't declare fields outside the constructor.
Starting Scala 2.13, case classes (as implementations of Product) are provided with a productElementNames method which returns an iterator over their field's names.
By zipping field names with field values obtained with productIterator we can generically obtain the associated Map:
// case class MyClass(param1: String, param2: String)
// val x = MyClass("hello", "world")
(x.productElementNames zip x.productIterator).toMap
// Map[String,Any] = Map("param1" -> "hello", "param2" -> "world")
If anybody looks for a recursive version, here is the modification of #Andrejs's solution:
def getCCParams(cc: Product): Map[String, Any] = {
val values = cc.productIterator
cc.getClass.getDeclaredFields.map {
_.getName -> (values.next() match {
case p: Product if p.productArity > 0 => getCCParams(p)
case x => x
})
}.toMap
}
It also expands the nested case-classes into maps at any level of nesting.
Here's a simple variation if you don't care about making it a generic function:
case class Person(name:String, age:Int)
def personToMap(person: Person): Map[String, Any] = {
val fieldNames = person.getClass.getDeclaredFields.map(_.getName)
val vals = Person.unapply(person).get.productIterator.toSeq
fieldNames.zip(vals).toMap
}
scala> println(personToMap(Person("Tom", 50)))
res02: scala.collection.immutable.Map[String,Any] = Map(name -> Tom, age -> 50)
If you happen to be using Json4s, you could do the following:
import org.json4s.{Extraction, _}
case class MyClass(param1: String, param2: String)
val x = MyClass("hello", "world")
Extraction.decompose(x)(DefaultFormats).values.asInstanceOf[Map[String,String]]
Solution with ProductCompletion from interpreter package:
import tools.nsc.interpreter.ProductCompletion
def getCCParams(cc: Product) = {
val pc = new ProductCompletion(cc)
pc.caseNames.zip(pc.caseFields).toMap
}
You could use shapeless.
Let
case class X(a: Boolean, b: String,c:Int)
case class Y(a: String, b: String)
Define a LabelledGeneric representation
import shapeless._
import shapeless.ops.product._
import shapeless.syntax.std.product._
object X {
implicit val lgenX = LabelledGeneric[X]
}
object Y {
implicit val lgenY = LabelledGeneric[Y]
}
Define two typeclasses to provide the toMap methods
object ToMapImplicits {
implicit class ToMapOps[A <: Product](val a: A)
extends AnyVal {
def mkMapAny(implicit toMap: ToMap.Aux[A, Symbol, Any]): Map[String, Any] =
a.toMap[Symbol, Any]
.map { case (k: Symbol, v) => k.name -> v }
}
implicit class ToMapOps2[A <: Product](val a: A)
extends AnyVal {
def mkMapString(implicit toMap: ToMap.Aux[A, Symbol, Any]): Map[String, String] =
a.toMap[Symbol, Any]
.map { case (k: Symbol, v) => k.name -> v.toString }
}
}
Then you can use it like this.
object Run extends App {
import ToMapImplicits._
val x: X = X(true, "bike",26)
val y: Y = Y("first", "second")
val anyMapX: Map[String, Any] = x.mkMapAny
val anyMapY: Map[String, Any] = y.mkMapAny
println("anyMapX = " + anyMapX)
println("anyMapY = " + anyMapY)
val stringMapX: Map[String, String] = x.mkMapString
val stringMapY: Map[String, String] = y.mkMapString
println("anyMapX = " + anyMapX)
println("anyMapY = " + anyMapY)
}
which prints
anyMapX = Map(c -> 26, b -> bike, a -> true)
anyMapY = Map(b -> second, a -> first)
stringMapX = Map(c -> 26, b -> bike, a -> true)
stringMapY = Map(b -> second, a -> first)
For nested case classes, (thus nested maps)
check another answer
I don't know about nice... but this seems to work, at least for this very very basic example. It probably needs some work but might be enough to get you started? Basically it filters out all "known" methods from a case class (or any other class :/ )
object CaseMappingTest {
case class MyCase(a: String, b: Int)
def caseClassToMap(obj: AnyRef) = {
val c = obj.getClass
val predefined = List("$tag", "productArity", "productPrefix", "hashCode",
"toString")
val casemethods = c.getMethods.toList.filter{
n =>
(n.getParameterTypes.size == 0) &&
(n.getDeclaringClass == c) &&
(! predefined.exists(_ == n.getName))
}
val values = casemethods.map(_.invoke(obj, null))
casemethods.map(_.getName).zip(values).foldLeft(Map[String, Any]())(_+_)
}
def main(args: Array[String]) {
println(caseClassToMap(MyCase("foo", 1)))
// prints: Map(a -> foo, b -> 1)
}
}
commons.mapper.Mappers.Mappers.beanToMap(caseClassBean)
Details: https://github.com/hank-whu/common4s
With the use of Java reflection, but no change of access level. Converts Product and case class to Map[String, String]:
def productToMap[T <: Product](obj: T, prefix: String): Map[String, String] = {
val clazz = obj.getClass
val fields = clazz.getDeclaredFields.map(_.getName).toSet
val methods = clazz.getDeclaredMethods.filter(method => fields.contains(method.getName))
methods.foldLeft(Map[String, String]()) { case (acc, method) =>
val value = method.invoke(obj).toString
val key = if (prefix.isEmpty) method.getName else s"${prefix}_${method.getName}"
acc + (key -> value)
}
}
Modern variation with Scala 3 might also be a bit simplified as with the following example that is similar to the answer posted by Walter Chang above.
def getCCParams(cc: AnyRef): Map[String, Any] =
cc.getClass.getDeclaredFields
.tapEach(_.setAccessible(true))
.foldLeft(Map.empty)((a, f) => a + (f.getName -> f.get(cc)))