fix(pgtype): JSON DecodeValue returns raw bytes for literal 'null' (#2430)#2570
Open
luongs3 wants to merge 1 commit into
Open
fix(pgtype): JSON DecodeValue returns raw bytes for literal 'null' (#2430)#2570luongs3 wants to merge 1 commit into
luongs3 wants to merge 1 commit into
Conversation
…ackc#2430) JSONCodec.DecodeValue and JSONBCodec.DecodeValue unmarshal into a Go interface{}, which collapses the JSON document `null` to a Go nil. That makes a JSON null literal indistinguishable from SQL NULL — both come out the rows.Values() / Map.Scan(&any) pipeline as nil. The vanilla database/sql interface does not have this problem because DecodeDatabaseSQLValue returns the raw response bytes, so QueryRow().Scan(&str) correctly yields "null" for `select 'null'::json`. Fix: * pgtype/json.go — when src is non-nil but Unmarshal yields nil (i.e. the JSON document was literally `null`), return a copy of the raw src bytes instead. SQL NULL (src nil) continues to return Go nil. * pgtype/jsonb.go — same change applied after the binary-format header is stripped. Non-null JSON values (`"hello"`, `42`, `[1,2,3]`, `{"k":"v"}`) continue to unmarshal into Go values as before — only the JSON-null-but- not-SQL-NULL case changes, and only to make it distinguishable from SQL NULL. Tests: * pgtype/json_2430_test.go — TestJSONCodecDecodeValueJSONNullLiteral exercises the codec directly (no DB needed) across 7 cases: - JSON SQL-NULL still returns Go nil - JSONB SQL-NULL still returns Go nil - JSON literal `null` now returns []byte("null") - JSONB literal `null` (text + binary formats) returns []byte("null") - JSON string value still unmarshals to Go string - JSONB number value still unmarshals to Go float64 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Owner
|
As I mentioned in the original issue, I'm not convinced that the current behavior is wrong. But even if it is, having JSON |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2430.
Problem
JSONCodec.DecodeValueandJSONBCodec.DecodeValueunmarshal into a Gointerface{}, which collapses the JSON documentnullto a Gonil. That makes a JSONnullliteral indistinguishable from SQL NULL — both come out of therows.Values()/Map.Scan(&any)pipeline asnil.The vanilla
database/sqlinterface does not have this problem, becauseDecodeDatabaseSQLValuereturns the raw response bytes — soQueryRow().Scan(&str)correctly yields"null"forselect 'null'::json;.Repro
Fix
When
srcis non-nil butc.Unmarshal(src, &dst)yieldsdst == nil(i.e. the JSON document was literallynull, ornullwith whitespace), return a copy of the rawsrcbytes instead of the Go nil. SQL NULL (src == nil) continues to return Go nil as before.Applied to:
pgtype/json.go—JSONCodec.DecodeValuepgtype/jsonb.go—JSONBCodec.DecodeValue(same change, applied after the binary-format version byte is stripped)What does NOT change
Other JSON values (
"hello",42,[1,2,3],{"k":"v"}) continue to unmarshal into Go values exactly as before. The only behavior change is the JSON-null-but-not-SQL-NULL case, and only to make it distinguishable from SQL NULL.Tests
pgtype/json_2430_test.goaddsTestJSONCodecDecodeValueJSONNullLiteral— 7 table-driven sub-cases exercising the codec directly (no DB required):json/sql_null— SQL NULL still returns Go niljsonb/sql_null/text— same for JSONBjson/json_null_literal— JSONnullnow returns[]byte("null")jsonb/json_null_literal/text— JSONBnull(text format) returns[]byte("null")jsonb/json_null_literal/binary— JSONBnull(binary format) returns[]byte("null")after stripping the v1 header bytejson/string—"hello"still unmarshals to Go string"hello"jsonb/number/text—42still unmarshals to Gofloat64(42)All pass with the fix; the existing JSON tests that require a live PostgreSQL (
TestJSONCodecScanNull, etc.) are unchanged.