Pattern match Jackson JSON in Scala - scala

I am wondering if it is possible to do pattern matching on Jackson JSON objects in Scala. We are currently using jackson-module-scala in a Project heavily and would benefit from being able to do pattern matching of Json ObjectNode/JsonNode objects.
If this is not possible, how would I go about adding this functionality? I was thinking something in terms of implicit conversion from JsonNode/ObjectNode to MyClass, where MyClass would have unapply method, doing JsonNode.toString and regex matching. If my logic is correct, I could then do pattern matching on JsonNode objects. Of course, there could be better ways I am not aware of, or this one may not work for reasons I am not yet aware of. To illustrate my case, I would like to be able to perform something in terms of:
val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)
val json = mapper.createObjectNode()
.put("key1","value1")
.put("key2","value2")
.put("key3","value3")
json match {
case MyClass("key1", "value1", "key2", y) => println("Found key1 with value1, where key2 is " + y)
case MyClass("key1", x) => println("Key1 value is " + x)
...
_ => println("No match found")
}

Have you tried to make use of the case class deserialization?
https://github.com/FasterXML/jackson-module-scala/blob/master/src/test/scala/com/fasterxml/jackson/module/scala/deser/CaseClassDeserializerTest.scala
If that doesn't work, I think you would be better off creating extractors to represent your domain objects. Code below assumes a scala Map, but it should give you an idea. No implicit conversion required.
case class DomainObjectA(v1: String, v2: String)
object DomainObjectAExtractor {
def unapply(m: Map[String, String]) = for {
v1 <- m.get("key1")
v2 <- m.get("key2")
} yield DomainObjectA(v1, v2)
}
case class DomainObjectB(v3, v4, v5)
object DomainObjectBExtractor {
def unapply(m: Map[String, String]) = for {
v3 <- m.get("key3")
v4 <- m.get("key4")
v5 <- m.get("key5")
} yield DomainObjectB(v3, v4, v5)
}
json match {
case DomainObjectAExtractor(a#DomainObjectA(_, _)) => a
case DomainObjectBExtractor(b#DomainObjectB(_, _, _)) => b
}
However, if you insist on trying to match against the key/value pairs, there may be ways to accomplish something which is acceptable for you. It is not possible to pass input into the unapply function from the case, which I think would be required if I understand what you want to do correctly. It may be possible to do this with macros which are experimental in the soon-to-be-officially-released scala 2.10. I haven't played with them enough to know if this is or is not possible though.
If ordering of keys was assumed, you could come up with a :: unapply operator similar to :: for list. This could extract the K, V pairs in this known order. Personally, this is too fragile for my tastes.
val json = Map(("key1" -> "one"), ("key2" -> "two"))
object -> {
def unapply[A, B](ab: (A, B)) = Some(ab)
}
object :: {
def unapply[K, V](m: Map[K, V]): Option[((K, V), Map[K, V])] =
m.headOption.map(_ -> m.tail)
}
scala> json match {
| case ("key1" -> "one") :: ("key2" -> value2) :: _ => value2
| }
res0: java.lang.String = two
You would not be able to extract keys in the wrong order though
scala> json match {
| case ("key2" -> value2) :: _ => value2
| case _ => "match fail"
| }
res2: java.lang.String = match fail
You could write Key1, Key2, Key3 as well. This may or may not scale well.
object && {
def unapply[A](a: A) = Some((a, a))
}
object Key2 {
def unapply[V](m: Map[String, V]) = m.get("key2")
}
object Key1 {
def unapply[V](m: Map[String, V]) = m.get("key1")
}
scala> json match {
| case Key2(value2) && Key1(value1) => (value2, value1)
| }
res5: (java.lang.String, java.lang.String) = (two,one)

Related

Map with different types to String

I am trying to learn some functional programming in Scala.
I have this Map:
val params: Map[String, QueryMap] = Map(
"a" -> SimpleQueryVal("1"),
"b" -> ComplexQueryVal("2", "3")
)
Where QueryMap is (might not be the best approach):
sealed trait QueryMap
case class SimpleQueryVal(value: String) extends QueryMap
case class ComplexQueryVal(values: String*) extends QueryMap
My result would be having a string like query parameters: ?a=1&b=2&b=3
I tried something, but my method return an Iterator[String] even I use mkString, looks ugly and I am sure that there's a very simple way of doing it.
def paramString(queryMap: Map[String, QueryMap]) = queryMap.keys.map { key =>
val params = queryMap(key) match {
case SimpleQueryVal(x) => "%s=%s".format(key, x)
case complexQuery: ComplexQueryVal => complexQuery.values.map { value =>
"%s=%s".format(key, value)
}
}
val result: String = params match {
case s: String => s + "&"
case s: ArrayBuffer[_] => s.mkString("&")
}
result.mkString
}
I would appreciate any idea that would make me learn something for today. :)
I think the result String can be built in a simpler, more straight forward, manner.
def paramString(queryMap: Map[String, QueryMap]): String = queryMap.map{
case (k, sq: SimpleQueryVal) => s"$k=${sq.value}"
case (k, cq: ComplexQueryVal)=> cq.values.map(k + "=" + _).mkString("&")
}.mkString("&")
A little cleaner:
def paramString(queryMap: Map[String, QueryMap]) = queryMap.flatMap {
case (key, SimpleQueryVal(x)) => Seq(s"$key=$x")
case (key, ComplexQueryVal(values # _*)) => values.map {v =>
s"$key=$v"
}
}.mkString("&")
No need for ArrayBuffer or to repeat the .mkString("&").
Keep in mind that this is good for just learning. If you're actually trying to handle HTTP query string parameters, you need to URLEncode the keys and the values and there's probably better libraries for that.
Try this:
def paramString(queryMap: Map[String, QueryMap]) = {
val qParams = queryMap.keys.map { key =>
queryMap(key) match {
case SimpleQueryVal(x) => "%s=%s".format(key, x)
case complexQuery: ComplexQueryVal => complexQuery.values.map { value =>
"%s=%s".format(key, value)
}.mkString("&")
}
}
qParams.mkString("&")
}
println(paramString(params))
Here, first you get a Set[String] like a=1 or b=2&b=3. Then you simply do another .mkString("&") to concatenate them all.

Scala check a Sequence of Eithers

I want to update a sequence in Scala, I have this code :
def update(userId: Long): Either[String, Int] = {
Logins.findByUserId(userId) map {
logins: Login => update(login.id,
Seq(NamedParameter("random_date", "prefix-" + logins.randomDate)))
} match {
case sequence : Seq(Nil, Int) => sequence.foldLeft(Right(_) + Right(_))
case _ => Left("error.logins.update")
}
}
Where findByUserId returns a Seq[Logins] and update returns Either[String, Int] where Int is the number of updated rows,
and String would be the description of the error.
What I want to achieve is to return an String if while updating the list an error happenes or an Int with the total number of updated rows.
The code is not working, I think I should do something different in the match, I don't know how I can check if every element in the Seq of Eithers is a Right value.
If you are open to using Scalaz or Cats you can use traverse. An example using Scalaz :
import scalaz.std.either._
import scalaz.std.list._
import scalaz.syntax.traverse._
val logins = Seq(1, 2, 3)
val updateRight: Int => Either[String, Int] = Right(_)
val updateLeft: Int => Either[String, Int] = _ => Left("kaboom")
logins.toList.traverseU(updateLeft).map(_.sum) // Left(kaboom)
logins.toList.traverseU(updateRight).map(_.sum) // Right(6)
Traversing over the logins gives us a Either[String, List[Int]], if we get the sum of the List we get the wanted Either[String, Int].
We use toList because there is no Traverse instance for Seq.
traverse is a combination of map and sequence.
We use traverseU instead of traverse because it infers some of the types for us (otherwise we should have introduced a type alias or a type lambda).
Because we imported scalaz.std.either._ we can use map directly without using a right projection (.right.map).
You shouldn't really use a fold if you want to exit early. A better solution would be to recursively iterate over the list, updating and counting successes, then return the error when you encounter one.
Here's a little example function that shows the technique. You would probably want to modify this to do the update on each login instead of just counting.
val noErrors = List[Either[String,Int]](Right(10), Right(12))
val hasError = List[Either[String,Int]](Right(10), Left("oops"), Right(12))
def checkList(l: List[Either[String,Int]], goodCount: Int): Either[String, Int] = {
l match {
case Left(err) :: xs =>
Left(err)
case Right(_) :: xs =>
checkList(xs, (goodCount + 1))
case Nil =>
Right(goodCount)
}
}
val r1 = checkList(noErrors, 0)
val r2 = checkList(hasError, 0)
// r1: Either[String,Int] = Right(2)
// r2: Either[String,Int] = Left(oops)
You want to stop as soon as an update fails, don't you?
That means that you want to be doing your matching inside the map, not outside. Try is actually a more suitable construct for this purpose, than Either. Something like this, perhaps:
def update(userId: Long): Either[String, Int] = Try {
Logins.findByUserId(userId) map { login =>
update(login.id, whatever) match {
case Right(x) => x
case Left(s) => throw new Exception(s)
}
}.sum
}
.map { n => Right(n) }
.recover { case ex => Left(ex.getMessage) }
BTW, a not-too-widely-known fact about scala is that putting a return statement inside a lambda, actually returns from the enclosing method. So, another, somewhat shorter way to write this would be like this:
def update(userId: Long): Either[String, Int] =
Logins.findByUserId(userId).foldLeft(Right(0)) { (sum,login) =>
update(login.id, whatever) match {
case Right(x) => Right(sum.right + x)
case error#Left(s) => return error
}
}
Also, why in the world does findUserById return a sequence???

Specialization of Scala methods to a specific tags

I have a generic map with values, some of which can be in turn lists of values.
I'm trying to process a given key and convert the results to the type expected by an outside caller, like this:
// A map with some values being other collections.
val map: Map[String, Any] = Map("foo" -> 1, "bar" -> Seq('a', 'b'. 'a'))
// A generic method with a "specialization" for collections (pseudocode)
def cast[T](key: String) = map.get(key).map(_.asInstanceOf[T])
def cast[C <: Iterable[T]](key: String) = map.get(key).map(list => list.to[C].map(_.asIntanceOf[T]))
// Expected usage
cast[Int]("foo") // Should return 1:Int
cast[Set[Char]]("bar") // Should return Set[Char]('a', 'b')
This is to show what I would like to do, but it does not work. The compiler error complains (correctly, about 2 possible matches). I've also tried to make this a single function with some sort of pattern match on the type to no avail.
I've been reading on #specialized, TypeTag, CanBuildFrom and other scala functionality, but I failed to find a simple way to put it all together. Separate examples I've found address different pieces and some ugly workarounds, but nothing that would simply allow an external user to call cast and get an exception is the cast was invalid. Some stuff is also old, I'm using Scala 2.10.5.
This appears to work but it has a some problems.
def cast[T](m: Map[String, Any], k: String):T = m(k) match {
case x: T => x
}
With the right input you get the correct output.
scala> cast[Int](map,"foo")
res18: Int = 1
scala> cast[Set[Char]](map,"bar")
res19: Set[Char] = Set(a, b)
But it throws if the type is wrong for the key or if the map has no such key (of course).
You can do this via implicit parameters:
val map: Map[String, Any] = Map("foo" -> 1, "bar" -> Set('a', 'b'))
abstract class Casts[B] {def cast(a: Any): B}
implicit val doubleCast = new Casts[Double] {
override def cast(a: Any): Double = a match {
case x: Int => x.toDouble
}
}
implicit val intCast = new Casts[Int] {
override def cast(a: Any): Int = a match {
case x: Int => x
case x: Double => x.toInt
}
}
implicit val seqCharCast = new Casts[Seq[Char]] {
override def cast(a: Any): Seq[Char] = a match {
case x: Set[Char] => x.toSeq
case x: Seq[Char] => x
}
}
def cast[T](key: String)(implicit p:Casts[T]) = p.cast(map(key))
println(cast[Double]("foo")) // <- 1.0
println(cast[Int]("foo")) // <- 1
println(cast[Seq[Char]]("bar")) // <- ArrayBuffer(a, b) which is Seq(a, b)
But you still need to iterate over all type-to-type options, which is reasonable as Set('a', 'b').asInstanceOf[Seq[Char]] throws, and you cannot use a universal cast, so you need to handle such cases differently.
Still it sounds like an overkill, and you may need to review your approach from global perspective

Override toString behavior for Option[_] in scala

I would prefer to see just the value of the Option (if it were not None) , instead of the following additional Some() noise:
List((Some(OP(_)),Some(share),3), (Some(OP(D)),Some(shaara),4), (Some(OP(I)),Some(shaaee),4))
Now, I could write a method that handles this for List[Option[_]] .. But there are many other structures in which Options appear - so this approach of addressing explicitly each one is cumbersome.
Due to implicits having lower precedence the following code simply gets ignored:
implicit def toString(myopt : Option[_]) = if (myopt == None) "None" else myopt.get
The concern is that - although implementing for example a toString(List[Option_]]) method that handles this in the desired manner, that is still a one-off. What about a
Map[Option,Option] => def toString(Map[Option,Option]) = { .. }
It seems We would still need to implement an explicit toString() for each collection type..
I guess you can't override this behavior for toString, but you could use shows (and show) methods from scalaz. You could override behavior of these methods:
import scalaz._, Scalaz._
val l = List(1.some, none, 3.some, 4.some, none, 6.some)
l.shows
// res0: String = [Some(1),None,Some(3),Some(4),None,Some(6)]
implicit def optionShow[T: Show]: Show[Option[T]] =
Show.show{ _.map{_.show}.getOrElse(Cord("<none>")) }
l.shows
// res1: String = [1,<none>,3,4,<none>,6]
It works for all types with Show:
1.some.node(none.node(2.some.leaf)).drawTree
// 1
// |
// `- <none>
// |
// `- 2
Map(1.some -> 2.some, none[Int] -> 3.some).shows
// Map[1->2, <none>->3]
Well i would just write a Wrapper similar to your implicit.
class OW[T](val option : Option[T]) {
override def toString = if (option.isEmpty) "None" else option.get.toString
}
Then when i want the pretty toString, i would just map any collection to instances of OW.
println(List(Some(3), Some("Hello"), None).map(new OW(_)))
which prints: List(3, Hello, None)
Don't really see a better way.
Following takes care of the cases that came to mind:
def show(obj: Any) : String = {
obj match {
case o: Option[_] =>
if (o == None) {
"<none>"
} else {
show(o.get)
}
case i: Iterable[_] =>
i.map(show).mkString("[", ",", "]")
case m: Map[_, _] =>
m.map {
case (a, b) => List(show(a), show(b)).mkString(":")
}.mkString("{", ",", "}")
case e: Enumeration =>
e.toString
case c : Product if !c.getClass.getMethods.map(_.getName).contains("copy$default$2") =>
c.toString
case t: Product =>
t.productIterator.map(a => show(a)).mkString("(", ",", ")")
case _ =>
if (obj.isInstanceOf[AnyRef])
obj.asInstanceOf[AnyRef].toString
else
"" + obj
}
}
I opted to code this up to avoid adding scalaz dependency (assuming their Show class supports similar feature)

Pattern matching against Scala Map entries

Is there any Scala trick to enable pattern matching against map keys? In other words, I'd like to have an extractor that beside the Map instance accepted also a key value that would mean I want this pattern to match only if the matchable value is an instance of Map and there is an entry with the given key in it and the value for this entry be subject to recursive pattern matching.
Something like this:
myMap match {
case MyMap("a")(a) => // do smth with value a
case MyMap("b")(MyMap("c")(c)) => // do smth with value c
}
Update:
I've found some way to approach closer to the goal, but it's still not perfect because it implies definition of synthetic key-value-holders:
case class MapKey[K](key: K) {
def unapply(o: Any) = o match {
case m: Map[K, _] ⇒ m.get(key)
case _ ⇒ None
}
}
val m1 = Map("a" → "aa", "b" → Map("c" → "cc"))
val m2 = Map("a" → "aa", "d" → "dd")
val b = MapKey("b")
val c = MapKey("c")
val d = MapKey("d")
for (m ← List(m1, m2)) m match {
case b(c(x)) ⇒ println(s"b > c: $x")
case d(x) ⇒ println(s"d: $x")
}
Similar question: Can extractors be customized with parameters in the body of a case statement (or anywhere else that an extractor would be used)?
Feature request: SI-5435
Maybe you are looking for solution that you don't really need? Can't imagine extractors here. You can use PF if you want to match key-value pairs:
val map = Map[String, String]("a" -> "b")
def matchTuple[A,B,C](map: Map[A,B])(pf: PartialFunction[(A,B), C]) =
map.collectFirst(pf)
matchTuple(map) {
case ("a", b) => println("value for a is " + b)
}
Return type is Option[Unit] because we use collectFirst and println