Getting around the Option[Seq] constraint with play-json macros (Reads)

Not sure if cross-posting is ok, but trying my luck here…

Upon reading JSON documents in play-json, it seems the code generated by the macros expects sequences to be enclosed in an Option if they may be empty. Is there a way to prevent this?

Thanks!

Does this really go for empty collections, or null/absence of a collection?

Actually I had only tried with an absent value, but it turns it the same problem occurs with null but not []

The following illustrates the problem (this excerpt runs sucessfully):

  import play.api.libs.json._
  
  case class Foo1(bar: String, baz:        Seq[Int])
  case class Foo2(bar: String, baz: Option[Seq[Int]])
  
  implicit val FormatFoo1 = Json.format[Foo1]
  implicit val FormatFoo2 = Json.format[Foo2]
  
  def main(args: Array[String]): Unit = {       
    assert(Json.fromJson[Foo1](Json.parse("""{"bar":"hello","baz":[1, 2, 3]}""")).isSuccess)
    assert(Json.fromJson[Foo1](Json.parse("""{"bar":"hello","baz":[]       }""")).isSuccess)
    assert(Json.fromJson[Foo1](Json.parse("""{"bar":"hello","baz":null     }""")).isError  )
    assert(Json.fromJson[Foo1](Json.parse("""{"bar":"hello"                }""")).isError  )

    assert(Json.fromJson[Foo2](Json.parse("""{"bar":"hello","baz":[1, 2, 3]}""")).isSuccess)
    assert(Json.fromJson[Foo2](Json.parse("""{"bar":"hello","baz":[]       }""")).isSuccess)
    assert(Json.fromJson[Foo2](Json.parse("""{"bar":"hello","baz":null     }""")).isSuccess)
    assert(Json.fromJson[Foo2](Json.parse("""{"bar":"hello"                }""")).isSuccess)
  }

That seems correct to me as default behaviour: no collection is different from an empty collection.

Manually creating the Reads instance would work around. myopt.getOrElse(Nil) for example will flatten an Option[Seq[A]] into a List[A]

You’re right that I should rephrase my question, in that it’s not a “problem” with play-json.

Basically I was hoping for a workaround, one that would still allow me to use the macros (else it quickly becomes very verbose). But I suspect it’s going to be more work than just wrapping it in an Option :slight_smile:

The problem is: how should the case class be constructed if one of its parameters are missing?

What you could do, is to use default parameters:

case class Foo1(bar: String, baz: Seq[Int] = Seq.empty)

val implicit foo1Reads = Json.using[Json.MacroOptions with Json.DefaultValues].reads[Foo1]
3 Likes

Very nice thanks! I think that might be just what I needed. It’s unfortunate the documentation for the Macros doesn’t mention .using