gcvctor: marshal string Values in JSONFromNullable/PGJSONBFromNullable like the client#237
Conversation
#236) Drop the string special-case so both helpers always delegate to JSONValue/PGJSONBValue, matching the official client encodeValue: a Go string Value becomes a quoted JSON string on the wire. Pre-encoded wire JSON is passed as json.RawMessage, which jsonWireString already stores as-is (validated and compacted), the same convention the client follows. Closes #236. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates JSONFromNullable and PGJSONBFromNullable to marshal Go strings as quoted JSON strings, aligning with the official Spanner client's encodeValue behavior. To store pre-encoded wire JSON as-is, users must now pass it as json.RawMessage. The PR also updates the documentation, adds examples, and adjusts the unit tests to reflect and verify this behavior. I have no feedback to provide as the changes are clean, well-documented, and properly tested.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Closes #236. Feedback from the spanenc adoption of v0.7.2 (#232 / #207).
What
JSONFromNullableandPGJSONBFromNullableno longer special-case astringValue as pre-encoded wire JSON. They always delegate toJSONValue/PGJSONBValue, so a Go string marshals to a quoted JSON string on the wire — the same semantics as the official client'sencodeValue. Pre-encoded wire JSON is passed asjson.RawMessage, whichjsonWireString'sjson.Encoderalready stores as-is (validated and compacted, no HTML escaping); the client follows the samejson.Marshalconvention forRawMessage, keeping the two aligned."x"(string)x(invalid JSON)"x""x"json.RawMessage("{\"a\":1}"){"a":1}(via marshal){"a":1}{"a":1}Why this is a bug fix, not a design change
These helpers take the client's own wrapper types, whose
Valuesemantics the client defines: a client-decoded JSON string column yields a GostringinNullJSON.Value, and re-encoding it through the v0.7.2 helpers corrupts the round trip ("x"becomes barex, which is not valid JSON wire). Interpreting client-typed input differently from the client is a contract violation, and the output is malformed — the same class as the v0.7.1 wire-correctness fixes (TimestampValueUTC normalization, JSON HTML-escape removal), which shipped as a patch.The divergence is also what kept spanenc on its hand-rolled
encodeNullJSON/encodePGJsonB(apstndb/spanenc@662f7a8). With this fix spanenc can adopt these helpers.json.RawMessageis the establishedencoding/jsonidiom for pre-encoded payloads, so the double-marshal-avoidance use case keeps an explicit, client-compatible spelling.Changes
gcvctor/nullable_input.go: drop the string special-case in both helpers; document the marshal semantics and theRawMessagepath.gcvctor/doc.go: note the behavior in the nullable-inputs section.gcvctor/example_test.go:ExampleJSONFromNullablepinning string-vs-RawMessage wire output.gcvctor/nullable_input_test.go: update string-Value expectations to quoted wire; addRawMessagepass-through and invalid-RawMessageerror cases.Release note
Recommend shipping as a patch (v0.7.3): it fixes invalid wire output from a helper introduced in v0.7.2 (released 2026-06-11) with no known adopters — spanenc, the requester of #232, explicitly declined the special case. Suggested release-notes row:
JSONFromNullable/PGJSONBFromNullablewithstringValuejson.RawMessagefor as-is🤖 Generated with Claude Code