Absent field aware Reads

18 views
Skip to first unread message

Nathan Fischer

unread,
Mar 6, 2018, 4:53:20 PM3/6/18
to Play framework dev
Currently play.api.libs.json.Reads defines its reads method like def reads(json: JsValue): JsResult[A], however with this signature it's not possible to distinguish between a null value and an absent field (the default Option reads would return None either way).

For an example of where this would be useful: it's impossible to implement json merge patch without it.

I'd propose to somehow work in a method like 
def reads(maybeJson: Option[JsValue]): JsResult[A] which would work something like this

case class Bag(js: JsValue)
implicit val bagReads: Reads[Bag] = Reads(_.map(js => JsSuccess(Bag(js)).getOrElse(JsError("field missing"))))

case class MyModel(bag: Bag)
implicit val modelReads = Json.reads[MyModel]

Json.fromJson[Bag](Json.parse("{}")) // JsError("field missing")

Json.fromJson[Bag](Json.parse("""{"bag": null}""")) // JsSuccess(Bag(JsNull))

Json.fromJson[Bag](Json.parse("""{"bag": "hi"}""")) // JsSuccess(Bag(JsString("hi"))

I think this would also allow the default optional field parsing to be implemented in the standard way like other default reads (and the default option writes for that matter).

Anyone else have interest in a feature like this or have ideas how it could be implemented?



James Roper

unread,
Mar 6, 2018, 7:06:45 PM3/6/18
to Nathan Fischer, Play framework dev
Hi Nathan,

The existence of a field is not a property of JsValue. Conceptually it makes no sense to say "please read this value that doesn't exist", conceptually, if a value doesn't exist, you don't read it. The existence of a field is a property of the JsObject that that field lives or doesn't on, and it's up to the reads handling the JsObject to decide what to do if the property does or doesn't exist. This is the right place to handle it, it means you can easily implement things like backwards compatible migrations where you try and read property X first, and if that doesn't exist, read property Y. If handling the non existence of a property was the responsibility of the Reads for that property, then there is no way it would be able to fall back to read a different property. It must be the responsibility of the reads for the object that the property lives on.

I don't see why a json merge patch couldn't be implemented with the current API - it's just that the processing has to be done on the parent JsObject, not on the child.

--
You received this message because you are subscribed to the Google Groups "Play framework dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework-dev+unsub...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
James Roper
Senior Octonaut

Lightbend – Build reactive apps!
Twitter: @jroper

Nathan Fischer

unread,
Mar 6, 2018, 9:14:58 PM3/6/18
to Play framework dev
You're right that merge patch could be implemented still one level up. I suppose I should have said it would be awkward to implement in a re-usable way (since you'd have to re-write the reads for the parent object by hand rather than implementing a reads for some three case algebraic data type).

Couldn't you still implement backwards compatible migrations if the proposed feature was added? True you couldn't deprecate an absent-sensitive field, but I'm not sure you could in any case.

Something like (forgive the dark formatting)

case class MyModel(timestamp: Instant)
val modelReads = Reads { json
val maybeDeprecatedWay = for {
zone ← (json \ "zone").as[Option[ZoneId]]
dateTime ← (json \ "dateTime").as[Option[LocalDateTime]]
} yield dateTime.atZone(zone).toInstant


maybeDeprecatedWay
.map(JsSuccess(_))
.getOrElse((json \ "timestamp").validate[Instant])
.map(MyModel)
}

is quite similar to how it would be done today for the example you gave. We haven't taken away the ability to handle things on the outer object reads, just added the ability to do more in the inner reads (which means we no longer need `asOpt`).

Nathan Fischer

unread,
Mar 6, 2018, 9:17:27 PM3/6/18
to Play framework dev
Let me correct myself real quick,
is quite similar to how it would be done today for the example you gave.
it's similar to how it COULD be done, more than one way to skin that cat.
 
Reply all
Reply to author
Forward
0 new messages