Frage

I need to convert this JSON

{ "matchItem": { "collection": { "fieldName": [ "value1", "value2" ] } } }

to this MongoDB projection:

{ "collection": { "$elemMatch": { "fieldName": "value1", "fieldName": "value2" } }

Here below is my code:

def toProjection(json: JsValue) = {
  json.getOpt(__ \ "matchItem").map { value =>
    val obj = value.as[JsObject]
    for (key <- obj.keys) { obj.getOpt(__ \ key).map { matchObj =>
      for (matchKey <- matchObj.as[JsObject].keys) { matchObj.getOpt(__ \ matchKey).map { items =>
        val fields = items.as[Seq[JsValue]].map(item => (matchKey -> item))
        seq += key -> Json.obj("$elemMatch" -> Json.obj(fields))
      }}
    }}
  }
  if (seq.length > 0) JsObject(seq) else json
}

val json = """{ "matchItem": { "collection": { "fieldName": [ "value1", "value2" ] } } }"""
val proj = toProjection(json)

This code does not compile and I always get the following error message:

[error] /home/j3d/Projects/test/app/services/Test.scala:82: type mismatch;
[error]  found   : Seq[(String, play.api.libs.json.JsValue)]
[error]  required: (String, play.api.libs.json.Json.JsValueWrapper)
[error]                 seq += fieldMaps.fromPublic(key) -> Json.obj("$elemMatch" -> Json.obj(fields))
[error]                                                                                       ^

I'm a bit lost. I know Json.obj is a helper method to build JsObject instances:

JsObject(Seq(
  "key1" -> JsString("value1"),
  "key2" -> JsString("value2")
  ...
))

... is equivalent to:

Json.obj("key1" -> "value1", "key2" -> "value2")

In my code above, the fields value is a Seq[(String, play.api.libs.json.JsValue)]... so I don't understand why it doesn't work. Any help would be really appreciated.

War es hilfreich?

Lösung

Here is the solution:

def toProjection(json: JsValue) = {
  json.getOpt(__ \ "matchItem").map { value =>
    val obj = value.as[JsObject]
    for (key <- obj.keys) { obj.getOpt(__ \ key).map { matchObj =>
      for (matchKey <- matchObj.as[JsObject].keys) { matchObj.getOpt(__ \ matchKey).map { items =>
        val fields = items.as[Seq[JsValue]].map(item => (matchKey -> item))
     // seq += key -> Json.obj("$elemMatch" -> Json.obj(fields))
        seq += key -> Json.obj("$elemMatch" -> JsObject(fields))
      }}
    }}
  }
  if (seq.length > 0) JsObject(seq) else json
}

Replacing Json.obj(fields) with JsObject(fields) does the trick.

I hope it helps.

Andere Tipps

Ended up here after implementing my own solution and thinking there must be an easier way. Looks like there isnt. And here's yet another solution that appears to work:

def flatten(update: JsObject): JsObject = {
    def f(key: String, value: JsValue): Seq[(String, JsValue)] = {
      value match {
        case o: JsObject => o.fields.flatMap(kv => f(s"$key.${kv._1}", kv._2))
        case v: JsValue => Seq(key -> v)
      }
    }
    JsObject(update.fields.flatMap(kv => f(kv._1, kv._2)))
  }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top