Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play/Scala how to prevent Json serialization of empty arrays?

I want to recursively write a class to Json, so I'm using the following implicit writes:

implicit val writesObject : Writes[Object] = (
  (__ \ "id").writeNullable[String] ~
  (__ \ "list").lazyWriteNullable(Writes.traversableWrites[Object](writesObject))
)(unlift(Object.unapply)

where Object is a class like this:

case class Object(id: Option[String], list: Option[Seq[Object]])

It works, however I would like to prevent it from printing anything if "list" is empty. For example:

I want:

{ id: "someID",
  list: [
          {
            id: "someOtherId"            
          }
        ] 
}

I currently get(but don't want):

{ id: "someID",
  list: [
          {
            id: "someOtherId"            
            list: []
          }
        ] 
}

How can I achieve this? I'm new to Play/Scala and not sure exactly what should I be looking at so any pointers would be helpful. I'm using Scala 2.2.1.

PS: I've checked Scala Json Combinators but didn't see any reference on how to get this done.

Update:

So my issue is not that list is null, but that list is empty. That's why lazyWriteNullable wasn't working.

Testing johanandren answer I came up with the following extension to JsPath that returns Option[T] and supports the lazy format for recursive writes:

def lazyWriteNullableIterable[T <: Iterable[_]](w: => Writes[T]): OWrites[Option[T]] = OWrites((t: Option[T]) => {
  if(t != null) {
    t.getOrElse(Seq.empty).size match {
      case 0 => Json.obj()
      case _ => Writes.nullable[T](path)(w).writes(t)
    }
  }
  else {
    Json.obj()
  }
})

Thanks

like image 728
jmend Avatar asked Feb 15 '23 00:02

jmend


1 Answers

You can create a custom OFormat that will do this. By implicitly decorating JsPath with it you can include it in your json combinator definitions:

implicit class PathAdditions(path: JsPath) {

  def readNullableIterable[A <: Iterable[_]](implicit reads: Reads[A]): Reads[A] =
    Reads((json: JsValue) => path.applyTillLast(json).fold(
      error => error,
      result => result.fold(
        invalid = (_) => reads.reads(JsArray()),
        valid = {
          case JsNull => reads.reads(JsArray())
          case js => reads.reads(js).repath(path)
        })
    ))

  def writeNullableIterable[A <: Iterable[_]](implicit writes: Writes[A]): OWrites[A] =
    OWrites[A]{ (a: A) =>
      if (a.isEmpty) Json.obj()
      else JsPath.createObj(path -> writes.writes(a))
    }

  /** When writing it ignores the property when the collection is empty,
    * when reading undefined and empty jsarray becomes an empty collection */
  def formatNullableIterable[A <: Iterable[_]](implicit format: Format[A]): OFormat[A] =
    OFormat[A](r = readNullableIterable(format), w = writeNullableIterable(format))

}

This would allow you to create formats/reads/writes using the json combinator syntax like this:

case class Something(as: List[String], v: String)

import somewhere.PathAdditions    
val reads: Reads[Something] = (
  (__ \ "possiblyMissing").readNullableIterable[List[String]] and
  (__ \ "somethingElse").read[String]
)(Something)

val writes: Writes[Something] = (
  (__ \ "possiblyMissing").writeNullableIterable[List[String]] and
  (__ \ "somethingElse").write[String]
)(unlift(Something.unapply))

val format: Format[Something] = (
  (__ \ "possiblyMissing").formatNullableIterable[List[String]] and
  (__ \ "somethingElse").format[String]
)(Something, unlift(Something.unapply))
like image 117
johanandren Avatar answered Feb 16 '23 13:02

johanandren