Introduction
Real-world JSON data often contains null values, inconsistent formatting, and mixed types that need cleaning before use. PostgreSQL provides functions to strip nulls, format JSON for readability, and convert between JSON and relational formats. PostgreSQL 18 enhances these capabilities with new options for null handling.
Key Concepts
- jsonb_strip_nulls(): Recursively removes all object keys that have null values from a JSONB document.
- strip_in_arrays parameter (PG18): A new boolean parameter in PostgreSQL 18 that extends
jsonb_strip_nullsto also remove null elements from arrays. - jsonb_pretty(): Formats a JSONB value as indented, human-readable text.
- jsonb_array_elements() / jsonb_each(): Set-returning functions that expand arrays to rows or objects to key-value pairs.
- Improved null-to-scalar casting (PG18): PostgreSQL 18 returns SQL NULL instead of an error when casting a JSONB null to a scalar type.
Real World Context
API responses and imported data frequently contain null fields that inflate storage and complicate queries. Stripping nulls before storage reduces column size and simplifies downstream processing. The PostgreSQL 18 strip_in_arrays parameter addresses a long-standing pain point where null array elements survived jsonb_strip_nulls. Data pipeline teams use jsonb_array_elements and jsonb_each to convert JSON into relational form for analytics.
Deep Dive
jsonb_strip_nulls: Remove Null Values
This function recursively removes keys with null values from objects:
sql-- Remove null keys recursively SELECT jsonb_strip_nulls( '{"a": 1, "b": null, "c": {"d": null, "e": 2}}'::jsonb ); -- Result: {"a": 1, "c": {"e": 2}}
Note that jsonb_strip_nulls removes keys with null values from objects but, by default, leaves null elements in arrays untouched.
PostgreSQL 18: strip_in_arrays Parameter
PostgreSQL 18 adds a second parameter to jsonb_strip_nulls that also removes null elements from arrays:
sql-- Default behavior: nulls in arrays preserved SELECT jsonb_strip_nulls( '{"items": [1, null, 3, null, 5]}'::jsonb ); -- Result: {"items": [1, null, 3, null, 5]} -- PG18: strip_in_arrays removes array nulls too SELECT jsonb_strip_nulls( '{"items": [1, null, 3, null, 5]}'::jsonb, true -- strip_in_arrays ); -- Result: {"items": [1, 3, 5]} -- Works recursively on nested structures SELECT jsonb_strip_nulls( '{"data": {"tags": ["a", null, "b"], "meta": null}}'::jsonb, true ); -- Result: {"data": {"tags": ["a", "b"]}}
This is particularly useful for cleaning up imported data or API responses that contain sparse arrays.
PostgreSQL 18: Improved JSONB Null-to-Scalar Casting
PostgreSQL 18 improves how JSONB null values are handled when casting to SQL types:
sql-- PG17 and earlier: error when casting jsonb null to scalar -- SELECT ('null'::jsonb)::text; -- ERROR -- PG18: returns SQL NULL instead of error SELECT ('null'::jsonb)::text; -- NULL SELECT ('null'::jsonb)::integer; -- NULL
This change eliminates a common source of runtime errors when processing JSON data that may contain null values.
jsonb_pretty: Format for Readability
Format JSONB as indented text for debugging and logging:
sqlSELECT jsonb_pretty('{"name":"Alice","address":{"city":"NYC"}}'::jsonb); -- Result: -- { -- "name": "Alice", -- "address": { -- "city": "NYC" -- } -- }
This is invaluable for debugging complex JSON structures in psql.
Type Conversions
Convert between JSON and relational formats:
sql-- Convert JSONB array to rows SELECT * FROM jsonb_array_elements('[1, 2, 3]'::jsonb); -- Returns rows: 1, 2, 3 SELECT * FROM jsonb_array_elements_text('["a", "b", "c"]'::jsonb); -- Returns text rows: a, b, c -- Expand object to key-value pairs SELECT * FROM jsonb_each('{"a": 1, "b": 2}'::jsonb); -- Returns: (a, 1), (b, 2) SELECT * FROM jsonb_each_text('{"a": 1, "b": 2}'::jsonb); -- Returns text: (a, '1'), (b, '2')
These functions are essential for bridging JSON data into relational queries.
COALESCE for Defaults
Always provide default values for missing keys:
sql-- Provide default values for missing keys SELECT COALESCE(data->>'email', 'no-email') AS email, COALESCE((data->>'age')::int, 0) AS age, COALESCE(data->'settings', '{}'::jsonb) AS settings FROM users;
This prevents NULL propagation in downstream calculations.
Common Pitfalls
- Expecting jsonb_strip_nulls to remove array nulls without strip_in_arrays — By default, only object key nulls are removed. Use the PG18
trueparameter for arrays. - Using jsonb_pretty in production queries — It adds significant overhead and should only be used for debugging or logging, not in hot paths.
- Confusing JSONB null with SQL NULL —
'{"a": null}'::jsonb->'a'returns a JSONB null value, not SQL NULL. Use->>and COALESCE for safe extraction.
Best Practices
- Strip nulls before storing imported data — Run
jsonb_strip_nullson API responses to reduce storage and simplify queries. - Use strip_in_arrays on PG18+ for thorough cleaning — When your data contains sparse arrays, enable the new parameter.
- Always COALESCE extracted values — Provide sensible defaults for optional JSON fields to avoid NULL-related bugs.
Summary
jsonb_strip_nullsremoves object keys with null values recursively.- PostgreSQL 18 adds the
strip_in_arraysparameter to also remove null array elements. - PostgreSQL 18 improves null-to-scalar casting to return SQL NULL instead of errors.
jsonb_prettyformats JSON for human readability (debugging only).jsonb_array_elementsandjsonb_eachconvert JSON to relational rows.- Always use COALESCE when extracting optional JSON values.
Code Examples
-- Clean API response before storing (PG18 with strip_in_arrays)
INSERT INTO api_cache (endpoint, response)
VALUES (
'/users',
jsonb_strip_nulls(
'{"users": [{"id": 1, "name": "Alice", "deleted": null}],
"errors": [null, null]}'::jsonb,
true -- strip_in_arrays (PG18)
)
);
-- Expand JSON array to rows for processing
SELECT
o.id AS order_id,
item->>'product' AS product,
(item->>'quantity')::int AS quantity
FROM orders o,
jsonb_array_elements(o.data->'items') AS item
WHERE o.status = 'pending';