3
votes

Welcome,

I really wanted to avoid this but i give up for now. I am trying to traverse through json array and withdraw some elements and put them to sequence. I tried many options, tried all from play docs, many attempts and still i have the same error: null pointer exception when my method try to withdraw elements. So my json example is:

[
  {
    "var1": "xx",
    "var2": "xxx",
    "var3": 111
  },
  {
    "var1": "yy",
    "var2": "yyy",
    "var3": 222 
  },
  {
    "var1": "zz",
    "var2": "zzz",
    "var3": 333    
  }
]

I defined many configurations of Reads, last one is like that but doesnt want to compile even ;/

case class Vars1(vars: Seq[String])
val var1Reads: Reads[String] = (__ \ "var1").read[String]
implicit val vars1Reads: Reads[Vars1] = ((__).read[Seq[String]])(Vars1.apply _)

I want to create Sequence or List, this not important, from only var1 from each element of this array. It can be pure sequence, or case class with sequence or other. Just how to iterate through json arrays? I tried many configurations, reading var1 is nonproblematic, but when i want read all vars 1 from each element of array then i have null pointer exception. Please, anyone can point me in a good direction, give me a hint what i am doing wrong? Please don't blame I am newbie at programming, and i started to learn scala and play framework.

Thank you for any help.

Update:

I have tried this one too:

implicit val vars1Reads = (__).read(Reads.list((__).read[String])).map( var => Vars(var))

but still i have the same error:

[NoSuchElementException: JsError.get]
1
Declare your reads to be lazy, e.g., implicit lazy val vars1Reads. That's an annoying and altogether confusing source of NPEs.Ryan

1 Answers

3
votes

The easiest way of parsing this is to let Play parse the array body as a Seq[_], that is, a sequence of objects.

I would suggest that each item in your array should be represented by a case class, e.g.

case class Var1(var1: String)

This provides a useful type upon which to add validation of the input data from the JSON.

You can add an implicit Reads in an appropriate location, e.g.

object Var1 {
  implicit val var1Reads: Reads[Var1] = {
    ((__ \ "var1").read[String]).map(Var1.apply _)
  }
}

Note that the read/write macros have a limitation where they do not work in the documented way where the case class has a single field - see http://grokbase.com/t/gg/play-framework/131bx7pcyd/play2-1-scala-json-writing-a-reads-writes-for-a-single-field-case-class - This is why the map call is added. The macro should work as documented when there are 2 or more fields.

You may then use this to create a Seq[Var1], e.g.

def test = Action(BodyParsers.parse.json) { request =>
  val result = request.body.validate[Seq[Var1]]
  ...
}

And the result (here I am just printing out the value of result.toString) is:

$ curl --include --request POST --header "Content-type: application/json" --data '[{"var1": "3", "var2": "4"}, {"var1": "7"}]' http://localhost:9000/test
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 34

JsSuccess(List(Var1(3), Var1(7)),)

Update

As mentioned in the comments below, you can simplify the Reads[Var1] implementation using a macro like this:

implicit val var1Reads = Json.reads[Var1]

This will only do what you want, if the field is called var1, i.e.

case class Var1(var1: String)