valiant-0.1.0.0: src/Valiant/Binary/JSON.hs
{-# OPTIONS_GHC -Wno-orphans #-}
-- | Binary encode/decode for PostgreSQL @json@ and @jsonb@ types.
--
-- JSON binary format: raw UTF-8 JSON text (same as text format).
-- JSONB binary format: version byte (0x01) followed by UTF-8 JSON text.
--
-- We provide instances for @aeson@'s 'Value' type. For json columns,
-- encoding is just the JSON bytes. For jsonb, PG prepends a version byte
-- on the wire, but the extended query protocol handles this transparently
-- when using binary format — the version byte is part of the binary
-- representation that PG sends/expects.
module Valiant.Binary.JSON
() where
import Data.Aeson (Value, eitherDecodeStrict', encode)
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as LBS
import PgWire.Binary.Types (PgDecode (..), PgEncode (..))
import PgWire.Protocol.Oid (oidJson)
-- | Encode a JSON Value. Works for both json and jsonb columns.
-- For jsonb, PG expects a version byte (0x01) prefix in binary format.
instance PgEncode Value where
pgEncode = LBS.toStrict . encode
{-# INLINE pgEncode #-}
pgOid _ = oidJson
{-# INLINE pgOid #-}
-- | Decode a JSON Value. Handles both json (raw JSON) and jsonb
-- (version byte + JSON) binary formats.
instance PgDecode Value where
pgDecode bs
-- jsonb binary format: first byte is version (0x01), rest is JSON
| not (BS.null bs) && BS.index bs 0 == 1 =
case eitherDecodeStrict' (BS.drop 1 bs) of
Left err -> Left ("jsonb decode: " <> err)
Right v -> Right v
-- json binary format: raw JSON bytes
| otherwise =
case eitherDecodeStrict' bs of
Left err -> Left ("json decode: " <> err)
Right v -> Right v
{-# INLINE pgDecode #-}