How to Retrieve the Raw On-Disk Binary Representation of JSONB in PostgreSQL
When working with PostgreSQL's JSONB type at high performance, developers often explore ways to bypass standard text serialization overhead. If you've tried fetching a JSONB column in binary mode (resultFormat = 1) using libpq and PQexecParams(), you might have noticed a surprising result: PostgreSQL sends a 1-byte version identifier (0x01) followed by plain UTF-8 JSON text.
Why does this happen, and is there a way to extract the true on-disk, byte-level representation of JSONB? Let's dive into how PostgreSQL serializes JSONB over the wire and explore how to access the underlying binary format.
Why Does jsonb_send() Emit Text Instead of Binary?
In PostgreSQL, each data type defines a send function responsible for converting the internal representation into a binary network stream. For JSONB, the server uses jsonb_send() defined in src/backend/utils/adt/jsonb.c:
/*
* jsonb type send function
*
* Just send jsonb as a version number, then a string of text
*/
Datum
jsonb_send(PG_FUNCTION_ARGS)
{
Jsonb *jb = PG_GETARG_JSONB_P(0);
StringInfoData buf;
/* ... serialize to text ... */
}
PostgreSQL intentionally does not send the raw binary format over network connections because:
- Cross-Architecture Portability: Machine architecture details (such as endianness and alignment) could cause compatibility issues between different server and client architectures.
- Version Stability: The on-disk layout of JSONB is an internal engine implementation detail. It can change between major PostgreSQL releases without breaking client drivers if the wire format remains standard JSON text.
How to Extract Raw JSONB On-Disk Bytes
If you are building an external client parser, you cannot get the raw disk bytes natively through PQexecParams without instructing PostgreSQL to treat those bytes as a raw byte array (bytea).
Approach 1: Create a Custom C Function (Recommended for Client Streaming)
Because you are already working with C and custom extensions, the cleanest and most efficient approach is to write a tiny C function that re-interprets the Jsonb varlena container as a bytea Datum.
#include "postgres.h"
#include "fmgr.h"
#include "utils/jsonb.h"
#include "utils/bytea.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(jsonb_as_raw_bytea);
Datum
jsonb_as_raw_bytea(PG_FUNCTION_ARGS)
{
/* Fetch the JSONB Datum, de-toasted if necessary */
Jsonb *jb = PG_GETARG_JSONB_P(0);
int32 len = VARSIZE(jb);
/* Allocate bytea container with the exact size */
bytea *result = (bytea *) palloc(len);
memcpy(result, jb, len);
PG_RETURN_BYTEA_P(result);
}
Expose this function in SQL:
CREATE FUNCTION jsonb_as_raw_bytea(jsonb)
RETURNS bytea
AS 'path/to/shared_library', 'jsonb_as_raw_bytea'
LANGUAGE C STRICT IMMUTABLE;
When you query your data using SELECT jsonb_as_raw_bytea(my_jsonb_col) FROM my_table with resultFormat = 1 in PQexecParams, PostgreSQL will invoke bytea_send. This sends the exact raw byte stream of the internal JSONB structure directly across the wire.
Understanding the On-Disk Layout of JSONB
Once you acquire the raw bytes, you need to parse the binary payload. PostgreSQL formats JSONB as a standard variable-length attribute (varlena), followed by:
- Varlena Header: 1 or 4 bytes depending on compression and TOAST status.
- Container Header: A 32-bit integer indicating the container type (
JB_FARRAY,JB_FOBJECT, orJB_FSCALAR) and the number of elements. - JEntry Array: A continuous array of 32-bit headers (one for each key/value or element), encoding data type flags and byte offsets.
- Payload Data: A contiguous blob containing actual key names, text values, numbers (in Postgres
NumericVarformat), or nested child containers.
You can refer to the core definitions in src/include/utils/jsonb.h to inspect the bit-masks and access macros like JEntry, JB_ROOT_IS_OBJECT, and JB_ROOT_COUNT.
A Critical Architectural Note: Client vs. Server Extension
If your ultimate goal is to build an extension that executes JSONPath queries faster than core PostgreSQL, consider running your logic inside the PostgreSQL backend rather than on a client:
- Zero Network Overhead: Extracting raw bytes to a client incurs socket transfer latency and serialization overhead.
- Direct Memory Pointer: Inside a Postgres C extension, calling
PG_GETARG_JSONB_P(0)yields a pointer directly to the in-memory or buffer-cached JSONB structure without any copying. - Memory-Efficient Traversal: PostgreSQL already traverses JSONB in-place without deserializing the whole document into an AST; standard functions navigate using the
JEntryoffset table. Custom JSONPath algorithms can inspect theJsonbContainerstructs directly in place.
Summary
PostgreSQL returns JSON text when requesting JSONB in binary mode to ensure architecture safety and protocol stability. To retrieve the actual binary disk format over libpq, write a lightweight C UDF that exposes the Jsonb datum as bytea, or perform your stream parsing directly inside the database backend where full memory-pointer access is already available.