Skip to content

gcvctor: JSONFromNullable/PGJSONBFromNullable string special-case diverges from the client; prefer json.RawMessage for pre-encoded wire #236

Description

@apstndb

Feedback from spanenc adoption of v0.7.2 (#232 / #207).

Problem

JSONFromNullable and PGJSONBFromNullable store a string Value as the wire JSON as-is. The official client's encodeValue (mirrored by spanenc) always json.Marshals NullJSON.Value / PGJsonB.Value, so a Go string becomes a quoted JSON string on the wire:

Input client wire gcvctor v0.7.2 wire
NullJSON{Value: "x", Valid: true} "x" x (invalid JSON)
NullJSON{Value: "{\"a\":1}" /* Go string */, Valid: true} "{\"a\":1}" {"a":1}

This made the helpers unusable for spanenc (which kept its own marshal-always encodeNullJSON / encodePGJsonB, see apstndb/spanenc@662f7a8), and it is a trap for anyone coming from the client: the wire meaning of Value: "x" silently changes with the dynamic type.

Note that a client-decoded JSON string column yields a Go string in NullJSON.Value; re-encoding through these helpers corrupts that round trip ("x" becomes x).

Proposal

Drop the string special-case and always delegate to JSONValue / PGJSONBValue. The double-marshal-avoidance use case is served by the established Go idiom for pre-encoded JSON, json.RawMessage, which jsonWireString's json.Encoder already passes through (validated and compacted, no HTML escaping) — and which the client itself also passes through, keeping the two aligned.

  • Value is a string: quoted JSON string (client-compatible)
  • Value is a json.RawMessage: wire as-is (the explicit pre-encoded path; client-compatible modulo the already-documented compact/HTML-escape cosmetic divergence)

With this change spanenc can replace its hand-rolled encodeNullJSON / encodePGJsonB with these helpers.

Impact

Runtime behavior change for string Values relative to v0.7.2 (released 2026-06-11; the special case shipped in #232 and has no known adopters — spanenc explicitly declined it). Release versioning is the maintainer's call; precedent: v0.7.1 shipped wire-correctness fixes as a patch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions