Reputation: 2794
I have a Seq
of JsValue
elements. Each element represents the following JSON structure with two fields:
{
"name": "xy"
"key ∈ {A,B,C}": ["// some values in an array"]
}
What this means is that I know the key of the first field (always "name"), but not the key of the array since it is "dynamic". But: the possible keys are known, it is either "A", "B" or "C".
What I want to do is to map each of these JsValue
objects to a case class:
case class Element(name: String, values: Seq[String])
As you can see, the name of the dynamic key is not even important. I just want to get the array that is associated with it.
But: how can I fetch the array with Reads[T]
if its key differs?
implicit val reads: Reads[Element] = (
(__ \ "name").read[String] and
(__ \ "???").read[Seq[String]]
)(Element.apply _)
Or does this have to be done "manually", if yes, how?
Upvotes: 1
Views: 1507
Reputation: 139048
As the other answer notes, orElse
works here, but if you want more flexibility you can always write something like a method that returns a Reads
that looks for a key that satisfies some predicate:
import play.api.libs.json._
def findByKey[A: Reads](p: String => Boolean): Reads[A] = Reads[A] {
case JsObject(fields) => fields.find(kv => p(kv._1)).map(
_._2.validate[A]
).getOrElse(JsError("No valid field key"))
case _ => JsError("Not an object")
}
And then:
import play.api.libs.functional.syntax._
case class Element(name: String, values: Seq[String])
object Element {
implicit val reads: Reads[Element] = (
(__ \ "name").read[String] and findByKey[Seq[String]](Set("A", "B", "C"))
)(Element.apply _)
}
And finally:
scala> Json.parse("""{ "name": "foo", "A": ["bar", "baz"] }""").asOpt[Element]
res0: Option[Element] = Some(Element(foo,List(bar, baz)))
scala> Json.parse("""{ "name": "foo", "A": [1, 2] }""").asOpt[Element]
res1: Option[Element] = None
Which approach you choose is a matter of taste, and will probably depend in part on whether the more general findByKey
is useful to you in other contexts.
Upvotes: 2
Reputation: 1824
You can use orElse
method
case class Element(name: String, values: Seq[String])
object Element {
implicit val reads: Reads[Element] = (
(__ \ "name").read[String] and
(__ \ "a").read[Seq[String]]
.orElse((__ \ "b").read[Seq[String]])
.orElse((__ \ "c").read[Seq[String]])
)(Element.apply _)
}
Upvotes: 1