zio / zio-json

Fast, secure JSON library with tight ZIO integration.
https://zio.dev/zio-json
Apache License 2.0
407 stars 146 forks source link

JsonCursor composition #1099

Closed BalduinLandolt closed 4 months ago

BalduinLandolt commented 5 months ago

While drafting #1098 I realized that the composition capabilities of JsonCursor are more limited than I would have expected:

The first thing I tried compiles but fails:

import zio.json._
import zio.json.ast.Json
import zio.json.ast.JsonCursor

val json1: Json = """{"posts": [{"id": 0, "title": "foo"}]}""".fromJson[Json].toOption.get
val json2: Json = """{"userPosts": [{"id": 1, "title": "bar"}]}""".fromJson[Json].toOption.get

val c1 = JsonCursor.field("posts")
val c2 = JsonCursor.field("userPosts")

val commonCursor = JsonCursor.isArray
  >>> JsonCursor.element(0)
  >>> JsonCursor.isObject
  >>> JsonCursor.field("title")
  >>> JsonCursor.isString

val cursor1 = c1 >>> commonCursor
val cursor2 = c2 >>> commonCursor

val title1 = json1.get(cursor1) // Left(Expected string but found [{"id":0,"title":"foo"}])
val title2 = json2.get(cursor2) // Left(Expected string but found [{"id":0,"title":"foo"}])

It would be nice if I could do something like the above, or like


val c1 = JsonCursor.field("posts")
val c2 = JsonCursor.field("userPosts")

val commonCursor = JsonCursor.isArray
  >>> JsonCursor.element(0)
  >>> JsonCursor.isObject
  >>> JsonCursor.field("title")
  >>> JsonCursor.isString

val cursor1 = c1.andThen(commonCursor)
val cursor2 = c2.andThen(commonCursor)

What I'm also missing is an or operator, like


val c1 = JsonCursor.field("posts")
val c2 = JsonCursor.field("userPosts")

val commonCursor = JsonCursor.isArray
  >>> JsonCursor.element(0)
  >>> JsonCursor.isObject
  >>> JsonCursor.field("title")
  >>> JsonCursor.isString

val cursor = c1.orElse(c2).andThen(commonCursor)

Might this be something that's useful to have? Or are there reasons not to support it?
I'd be happy to contribute, though I might need some pointers.