Pergunta

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.

Foi útil?

Solução

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.

Outras dicas

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)))
  }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top