0

I have a case class that's a "Bag of Fields" - lots of fields that represent a business entity. For my use case, the distinction between "", null, and the k/v being totally absent from the JSON object is irrelevant to me. In this case class these fields are already Option[String]s, so I'd like all 3 of those cases to collapse into None. I believe the current behavior of the auto-generated Reads is what I want except for the empty string case.

This BOF is likely to change in the future, so "just implement your own Reads" (as suggested here: make play-json read the empty string as None for a type of Option[T]) or something else where I have to re-enumerate all the fields is a non-starter.

I think what I may need is Play's 'Json Transformers'. It appears trivial to write a transformer that removes entries if they have empty values, but what I cant figure out is how to compose it with the auto-generated Reads implementation.

I imagine what I need is some combinator over Reads and transformers, some signature like: (JSON -> JSON, JSON -> T) -> (JSON -> T). Obviously I've found this page: https://www.playframework.com/documentation/2.5.x/ScalaJsonCombinators, but none of the listed combinators does what I want I believe. Could this combinator be easily implemented? I'd be a little out of my type-fu depth but that would be a great solution if I could get some pointers.

1 Answer 1

1

Here's what worked for me:

class RemoveEmpty[T] (reader: Reads[T]) extends Reads[T] {
  override def reads(json: JsValue): JsResult[T] = json match {
    case JsObject(underlying) => { 
      reader.reads(JsObject(underlying.filterNot{ case (k, v) => jsonValueEmpty(v) } ))
    }
    case _ => { 
      JsError("Non-JsObj passed to RemoveEmpty")
    }
  }

  def jsonValueEmpty(v: JsValue) = v match {
    case JsNull | JsString("") => true
    case _ => false
  }
}

Then you can just use it like this:

implicit val myTypeReads = new RemoveEmpty(Json.reads[MyType])
Sign up to request clarification or add additional context in comments.

2 Comments

FWIW I've never seen the error case hit after this function has seen considerable use - I presume Play is smart somehow, I don't particularly understand the innards of its JSON parsing
so cool! however it does not work with implicit val myTypeReads = new RemoveEmpty(play.api.libs.json.Reads.localDateTimeReads(DateTimeFormatter.ISO_DATE_TIME)); it throws your "Non-JsObj passed to RemoveEmpty" error. any idea?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.