-
Notifications
You must be signed in to change notification settings - Fork 356
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Circe throws an exception when parsing nested JSONB field that is null. #2123
Comments
In Postgres a JSON null and SQL null are different, so you probably need to cast it to a SQL null in the case of https://mbork.pl/2020-02-15_PostgreSQL_and_null_values_in_jsonb If it still doesn't work for you, can you help by provided a runnable minimal reproduction of the issue? |
Thank you for your suggestions, @jatcwang. Yep, I'm aware of the differences in arrows. In this simple case, the |
Here we go |
Thanks @slavaschmidt can you try something like Ideally the circe integration will be able to handle top level JSON nulls like your examples, but will need to think about it more because |
Thanks @jatcwang, unfortunately, this won't work. The first double-arrow returns TEXT, so the next double-arrow can't be applied. I checked it to be sure and the exception is: |
Ah I see sorry. Using e.g. I replaced line 53 in your example with and it yields |
The workaround works. Thank you very much for your help! Still, I believe it would be cool if JSON |
Another quick workaround that allows using SELECT profile->'address'->'coordinates' is to split the data processing into two steps: query it as sql"SELECT profile->'address'->'coordinates' FROM customer WHERE id = <any ID from the example>"
.query[Option[Json]]
.unique
.flatMap(_.flatTraverse {
_.as[Option[Coordinates]].liftTo[ConnectionIO]
}) This emits correct results regardless of whether the query returns SQL NULL or JSON NULL. |
Hi @satorg, thanks for the hint! This is precisely where I'm coming from. The issue is that with dozens of tables and lots of JSON columns, there are currently hundreds of LOCs dedicated to this manual parsing approach in our code base. Not only that. When a query returns multiple JSON columns, one must either work with tuples, which affects readability or create two different case classes, one with JSON and another with correctly typed fields. Using custom mappings elegantly solves both of these deficiencies. |
@jatcwang , the above workaround can be converted into a more generic solution. In order to accomplish that we need a constructor for def pgDecoderReadOption[A: Decoder]: Read[Option[A]] = {
val getJson = Get[Json]
new Read(
// To be honest, I cannot understand why do we need to store `Get` in `Read`.
// Doobie does it everywhere but doesn't seem using the stored value.
List((getJson, Nullability.Nullable)),
{ (rs, n) =>
getJson
.unsafeGetNullable(rs, n)
.flatTraverse(_.as[Option[A]])
// Unfortunately, cannot avoid `throw` from `Read` ¯\_(ツ)_/¯
.valueOr(throw _)
}
)
} Having such a constructor, a user can create their our type specific implicit val coordOptRead: Read[Option[Coordinates]] = pgDecoderReadOption[Coordinates] And now the original query from the example works for any sql"SELECT profile->'address'->'coordinates' FROM customer WHERE id = <any ID from the example>"
.query[Option[Coordinates]]
.unique Unfortunately, we cannot make |
If a query returns a nested structure from a JSON(b) field, the empty field must be absent for the query to work. If the field is present and
null
, there is a circe exception thrown.Example query:
sql"SELECT address->'coordinates' FROM profile"
Definition of the Coordinates and Address types:
Definition of the custom mapping:
The query works for the following contents of the
address
column:NULL
{}
The query throws and exception for the following contents of the
address
column:{"coordinates":null}
The exception message is:
io.circe.DecodingFailure$DecodingFailureImpl: DecodingFailure at : Got value 'null' with wrong type, expecting object
The text was updated successfully, but these errors were encountered: