Simplest way to read an array into a list of custom objects with Play JSON API
Asked Answered
I

2

1

I'm working with Play JSON API (latest version; Play 2.4), reading incoming JSON into objects.

When writing JSON, there's absolutely no problem in using a list of custom objects, as long as I have implicit val writes = Json.writes[CustomType].

But apparently the inverse is not true, as the following does not work even though Reads is generated for both top-level type and list item type (using Json.reads[Incoming] and Json.reads[Item]). Is custom Reads implementation mandatory? Or am I missing something obvious? What is the simplest way to make this work?

Simplified example:

JSON:

{
  "test": "...",
  "items": [
     { "id": 44, "time": "2015-11-20T11:04:03.544" },
     { "id": 45, "time": "2015-11-20T11:10:10.101" }
  ]
}

Models/DTOs matching the incoming data:

import play.api.libs.json.Json

case class Incoming(test: String, items: List[Item])

object Incoming {
  implicit val reads = Json.reads[Incoming]
}


case class Item(id: Long, time: String)

object Item {
  implicit val reads = Json.reads[Item]
}

Controller:

def test() = Action(parse.json) { request =>
  request.body.validate[Incoming].map(incoming => {
     // ... handle valid incoming data ...
  }).getOrElse(BadRequest)
}

Compiler has this to say:

No implicit format for List[models.Item] available.
[error]   implicit val reads = Json.reads[Incoming]
                       ^
No Json deserializer found for type models.Incoming. 
Try to implement an implicit Reads or Format for this type.
[error]     request.body.validate[Incoming].map(incoming => {
Impend answered 5/1, 2016 at 15:41 Comment(0)
L
3

Try defining the case class and object for Item before those for Incoming. See this answer for more info: https://stackoverflow.com/a/15705581

Leboff answered 5/1, 2016 at 16:21 Comment(1)
Thanks, this works too! So, both of the answers, independently, fix the problem. I think I'll prefer this one (to the import tweak), because this is less likely to break again accidentally (e.g. via having "Optimize imports" enabled while committing).Impend
A
1

The issue may be in your imports in Controller. Just importing "Incoming" will import the case class. In order to import the implicit val try "Incoming._". That will import all members of the Object Incoming.

Alienage answered 5/1, 2016 at 16:23 Comment(2)
Wow, you are right. It started working with these imports: import models.Incoming; import models.Incoming._; import models.Item._ So far I had trusted IntelliJ IDEA to handle and optimise imports for me, but in this case it failed.Impend
It will never be able to know which implicit variable to use because it could come from many different places, this will always have to be manual.Alienage

© 2022 - 2024 — McMap. All rights reserved.