Add a set of strings to an existing set with casbah - mongodb

I have a user object as follows:
{ user: "joe", acks: ["a", "b" ] }
I want to add a set of strings to the acks field. Here's my attempt to do this with one update:
def addSomeAcks(toBeAcked = Array[String])
DB.getCollection("userAcks").update(
MongoDBObject("user" -> "joe"),
$addToSet("acks") $each toBeAcked
)
}
def test() {
addSomeAcks(Set("x", "y", "z"))
}
When I run this code I get an embedded set as follows:
{ user: "joe", acks: ["a", "b", ["x", "y", "z" ] ] }
but the result I want is:
{ user: "joe", acks: ["a", "b", "x", "y", "z" ] }
I can make it work by calling update for each item in toBeAcked, is there a way to do this in one call?

The problem is that $each takes a variable number of arguments, not a collection type like Traversable. Because of that it treats the set that you pass as a single element and adds it to array as such. This leads to nesting as you observe. You need to unwrap it this way: $each(toBeAcked: _*) or pass each elem separately $each("x", "y", "z").
Here is a complete example that works as you'd expect it to:
package com.example
import com.mongodb.casbah.Imports._
object TestApp extends App {
val col = MongoConnection()("test")("userAcks")
def printAll(): Unit =
col.find().foreach(println)
def insertFirst(): Unit =
col.insert(MongoDBObject("user" -> "joe", "acks" -> List("a", "b")))
def addSomeAcks(toBeAcked: Seq[String]): Unit =
col.update(
MongoDBObject("user" -> "joe"),
$addToSet("acks") $each (toBeAcked: _*))
printAll()
insertFirst()
printAll()
addSomeAcks(Seq("x", "y", "z"))
printAll()
}

Related

Circe: Decode Each Member of Array to Case Class

I'm learning Circe and need help navigating json hierarchies.
Given a json defined as follows:
import io.circe._
import io.circe.parser._
val jStr = """
{
"a1": ["c1", "c2"],
"a2": [{"d1": "abc", "d2": "efg"}, {"d1": "hij", "d2": "klm"}]
}
"""
val j = parse(jStr).getOrElse(Json.Null)
val ja = j.hcursor.downField("a2").as[Json].getOrElse("").toString
ja
ja is now: [ { "d1" : "abc", "d2" : "efg" }, { "d1" : "hij", "d2" : "klm" } ]
I can now do the following to this list:
case class Song(id: String, title: String)
implicit val songDecoder: Decoder[Song] = (c: HCursor ) => for {
id <- c.downField("d1").as[String]
title <- c.downField("d2").as[String]
} yield Song(id,title)
io.circe.parser.decode[List[Song]](ja).getOrElse("")
Which returns what I want: List(Song(abc,efg), Song(hij,klm))
My questions are as follows:
How do I add item a1.c1 from the original json (j) to each item retrieved from the array? I want to add it to Song modified as follows: case class Song(id: String, title: String, artist: String)
It seems wrong to turn the json object back into a String for the iterative step of retrieving id and title. Is there a way to do this without turning json into String?

Converting JSON into Seq<String> in Scala

I'm new in Scala and trying to achieve the following using Scala's foldLeft() method or any other functional solution:
I have the following JSON:
{
"aspects": [
{
"name": "Name",
"values": [
"Phone"
]
},
{
"name": "Color",
"values": [
"Red",
"Black"
]
},
{
"name": "Size",
"values": [
"6",
"10"
]
}
]
}
I want to convert this into the following Seq<String>:
["Name:::Phone", "Color:::Red", "Color:::Black", "Size:::6", "Size:::10"]
I did that using Java style where aspects is an object representing the JSON:
aspects.foreach(pair => {
pair.values.foreach(value => {
valuesList += pair.name + ":::" + value
})
})
What is the best Scala's way to do this?
It depends on the JSON library you are using, but once you have the aspects data in a suitable format the output can be generated is like this:
case class Aspect(name: String, values: Seq[String])
val aspects: Seq[Aspect] = ???
aspects.flatMap(a => a.values.map(a.name + ":::" + _))
I use json4s and jackson and the conversion code is basically just
val aspects = parse(json).extract[Seq[Aspect]]
Check the documentation for details, and check out other JSON libraries which may be more suitable for your application.
Have not parsed the JSON since the object aspects has these values. Just manually constructing the case class and flatmapping it
scala> final case class Aspects(pairs:Seq[Pair])
defined class Aspects
val namepair = Pair("Name",Seq("Phone"))
namepair: Pair = Pair(Name,List(Phone))
val colorpair = Pair("Color",Seq("Red","Black"))
colorpair: Pair = Pair(Color,List(Red, Black))
val sizepair = Pair("Size",Seq("6","10"))
sizepair: Pair = Pair(Size,List(6, 10))
val aspects = Aspects(Seq(namepair,colorpair,sizepair))
aspects: Aspects = Aspects(List(Pair(Name,List(Phone)), Pair(Color,List(Red, Black)), Pair(Size,List(6, 10))))
aspects.pairs.flatMap(pair=>pair.values.map(value=>s"${pair.name}:::$value"))
res1: Seq[String] = List(Name:::Phone, Color:::Red, Color:::Black, Size:::6, Size:::10)

Merge nested json using play scala

I have two JSON object (with the same structure):
The first one is
json1 = {a: {b: [{c: "x" , d: val1}, {c: "y" , d: val2}]} }
and the second is
json2 = {a: {b: [{c: "x" , d: val3}, {c: "y" , d: val4}]} }
is there any way to merge these two object to have one object (if c value is same then sum d values):
result = {a: { b: [{c: "x", d: (val1+val3) } , {c: "y", d: (val2+val4) }] } }
if
json2 = {a: {b: [{c: "y" , d: val3}, {c: "z" , d: val4}]} }
result = {a: { b: [{c: "x" , d: val1} , {c: "y", d: (val2+val4+val3)},{c: "z" , d: val4}] } }
Is there any built in method to do this trick. Thanks.
If you know your JSON structure, one way of doing this would be to turn them into case classes and compare them. Here's a way I found (which is by no means optimised):
//Going by your JSON structure of {"a": {"b": [{"c": String , "d": Int}]}}
import play.api.libs.json.{Json, OFormat}
case class A(a: B)
object A{implicit val format: OFormat[A] = Json.format[A]}
case class B(b: Seq[C])
object B{implicit val format: OFormat[B] = Json.format[B]}
case class C(c: Option[String], d: Option[Int])
object C{implicit val format: OFormat[C] = Json.format[C]}
val json1 = Json.parse("""{"a": {"b": [{"c": "x" , "d": 1}, {"c": "y" , "d": 2}]}}""").as[A]
val json2 = Json.parse("""{"a": {"b": [{"c": "x" , "d": 3}, {"c": "y" , "d": 4}]}}""").as[A]
val cSeq: Seq[C] = {
(json1.a.b zip json2.a.b) map {
// List((C(Some(x),Some(1)),C(Some(x),Some(3))), (C(Some(y),Some(2)),C(Some(y),Some(4))))
c =>
val (c1, c2) = c
// assign a value to each element of the pairs
val BLANK_C = C(None, None)
if (c1.c.get == c2.c.get) C(c1.c, Some(c1.d.get + c2.d.get)) else BLANK_C
// if the "c" keys match, add the "d" keys. If not, return an empty C model
// will need to handle if get fails (ie None instead of Some(whatever))
}
}
val json3 = Json.toJson(A(B(cSeq)))
println(json3)
// {"a":{"b":[{"c":"x","d":4},{"c":"y","d":6}]}}
Currently, if the parts don't match then it returns an empty object. You didn't specify what you want to happen when they don't match so I'll leave that up to you to sort out.

How to update a document using ReactiveMongo

I get the following list of documents back from MongoDB when I find for "campaignID":"DEMO-1".
[
{
"_id": {
"$oid": "56be0e8b3cf8a2d4f87ddb97"
},
"campaignID": "DEMO-1",
"revision": 1,
"action": [
"kick",
"punch"
],
"transactionID": 20160212095539543
},
{
"_id": {
"$oid": "56c178215886447ea261710f"
},
"transactionID": 20160215000257159,
"campaignID": "DEMO-1",
"revision": 2,
"action": [
"kick"
],
"transactionID": 20160212095539578
}
]
Now, what I am trying to do here is for a given campaignID I need to find all its versions (revision in my case) and modify the action field to dead of type String. I read the docs and the examples they have is too simple not too helpful in my case. This is what the docs say:
val selector = BSONDocument("name" -> "Jack")
val modifier = BSONDocument(
"$set" -> BSONDocument(
"lastName" -> "London",
"firstName" -> "Jack"),
"$unset" -> BSONDocument(
"name" -> 1))
// get a future update
val futureUpdate = collection.update(selector, modifier)
I can't just follow the docs because its easy to create a new BSON document and use it to modify following the BSON structure by hardcoding the exact fields. In my case I need to find the documents first and then modify the action field on the fly because unlike the docs, my action field can have different values.
Here's my code so far which obviously does not compile:
def updateDocument(campaignID: String) ={
val timeout = scala.concurrent.duration.Duration(5, "seconds")
val collection = db.collection[BSONCollection](collectionName)
val selector = BSONDocument("action" -> "dead")
val modifier = collection.find(BSONDocument("campaignID" -> campaignID)).cursor[BSONDocument]().collect[List]()
val updatedResults = Await.result(modifier, timeout)
val mod = BSONDocument(
"$set" -> updatedResults(0),
"$unset" -> BSONDocument(
"action" -> **<???>** ))
val futureUpdate = collection.update(selector, updatedResults(0))
futureUpdate
}
This worked for me as an answer to my own question. Thanks #cchantep for helping me out.
val collection = db.collection[BSONCollection](collectionName)
val selector = BSONDocument("campaignID" -> campaignID)
val mod = BSONDocument("$set" -> BSONDocument("action" -> "dead"))
val futureUpdate = collection.update(selector, mod, multi = true)
If you have a look at the BSON documentation, you can see BSONArray can be used to pass sequence of BSON values.
BSONDocument("action" -> BSONArray("kick", "punch"))
If you have List[T] as values, with T being provided a BSONWriter[_ <: BSONValue, T], then this list can be converted as BSONArray.
BSONDocument("action" -> List("kick", "punch"))
// as `String` is provided a `BSONWriter`

Scala dynamic tuple type construction from sequence

I'm trying to generate some json using the json4s library which builds json using a dsl based around nested tuple structures. I have a scala Seq that I'd like to be able to convert to a nesting of tuples like so:
// scala Seq
val s = Seq("a", "b", "c", "d")
val rootItem = "id" -> 1234
// desired json
{
"a" : {
"b" : {
"c" : {
"d" : {
"id" : 1234
}
}
}
}
}
If I force it to ignore the types I can produce the desired tuple structure as follows:
// yields ("a", ("b", ("c", ("d", ("id", 1234)))))
s.foldRight[Any](rootItem)(_ -> _)
but because the type of the result is now denoted as Any the implicit conversion that write this to json don't fire (and throw an exception when called explicitly) despite the actual type being correct. I'm at a loss for how to construct this datastructure in a typesafe way. Ideally I'd like a solution that is able to appropriately build up the type, though I understand that it might be impossible since it requires information only available at runtime (the length of the list). I know that scala supports recursive types, which seem to potentially fit the bill but I haven't been able to understand how to work them in this case and don't know if they are safe for a 'real' system.
You're not going to be able to do this with a plain old fold, since the accumulator has to be the same type the whole time.
You can make the transformation to JSON as you go, however:
val s = Seq("a", "b", "c", "d")
val rootItem = "id" -> 1234
import org.json4s._
import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods._
val json = s.foldRight[JObject](rootItem)(_ -> _)
And then you can do the following, since json is statically typed as a JObject:
scala> pretty(render(json))
res0: String =
{
"a" : {
"b" : {
"c" : {
"d" : {
"id" : 1234
}
}
}
}
}
(As a footnote, there is a way you could do the fold with tuples and end up with the appropriate static type, but that's almost certainly not what you want in this case.)