質問

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.

役に立ちましたか?

解決

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.

他のヒント

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)))
  }
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top