PostgreSQL Error 2203E: Too Many JSON Object Members
PostgreSQL error code 2203E is raised when a JSON object exceeds the maximum number of allowed key-value pairs (members) during construction or processing. This typically surfaces when using JSON-building functions like json_build_object() or aggregation functions like json_object_agg() with an excessive number of entries. If you're hitting this error, your JSON design or data volume likely needs to be restructured.
Top 3 Causes
1. Passing Too Many Key-Value Pairs to json_build_object()
When you try to pack hundreds of columns or dynamically generated keys into a single json_build_object() call, PostgreSQL hits its internal limit and throws 2203E.
-- Problematic: too many key-value pairs in one call
SELECT json_build_object(
'col1', col1,
'col2', col2,
-- ... hundreds more
'col300', col300
)
FROM huge_table;
-- Fix: split and merge using jsonb || operator
SELECT
jsonb_build_object('col1', col1, 'col2', col2)
||
jsonb_build_object('col3', col3, 'col4', col4)
AS merged_result
FROM huge_table;
-- Alternative: use row_to_json for full row conversion
SELECT row_to_json(t) FROM huge_table t;
2. Unbounded json_object_agg() Aggregation
json_object_agg() merges all rows in a group into a single JSON object. When group sizes are very large — such as aggregating all user events or log entries — the resulting object can have thousands of members, triggering the error.
-- Problematic: no limit on aggregated rows
SELECT
user_id,
json_object_agg(event_key, event_value) AS all_events
FROM user_events
GROUP BY user_id;
-- Fix 1: filter before aggregating
SELECT
user_id,
json_object_agg(event_key, event_value) AS recent_events
FROM (
SELECT user_id, event_key, event_value
FROM user_events
WHERE created_at >= NOW() - INTERVAL '30 days'
) recent
GROUP BY user_id;
-- Fix 2: use jsonb_agg with array structure instead
SELECT
user_id,
jsonb_agg(
jsonb_build_object('key', event_key, 'value', event_value)
) AS events_array
FROM user_events
GROUP BY user_id;
3. Processing Unvalidated External JSON
JSON data from external APIs or third-party systems may contain far more members than expected. Without pre-validation, inserting or processing this data can trigger 2203E.
-- Check member count before processing
SELECT
id,
(SELECT COUNT(*) FROM jsonb_object_keys(raw_payload)) AS member_count
FROM incoming_data
WHERE (SELECT COUNT(*) FROM jsonb_object_keys(raw_payload)) > 500;
-- Safe extraction: pick only allowed keys
SELECT
id,
(
SELECT jsonb_object_agg(key, value)
FROM jsonb_each(raw_payload)
WHERE key = ANY(ARRAY['name', 'email', 'status'])
) AS safe_json
FROM incoming_data;
-- Add a trigger to enforce limits on insert
CREATE OR REPLACE FUNCTION enforce_json_member_limit()
RETURNS TRIGGER AS $$
BEGIN
IF (SELECT COUNT(*) FROM jsonb_object_keys(NEW.payload)) > 500 THEN
RAISE EXCEPTION '2203E: JSON object member limit exceeded';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_json_limit
BEFORE INSERT OR UPDATE ON incoming_data
FOR EACH ROW EXECUTE FUNCTION enforce_json_member_limit();
Quick Fix Solutions
| Scenario | Recommended Fix |
|---|---|
Too many columns in json_build_object
|
Split calls and merge with `\ |
Large {% raw %}json_object_agg result |
Add WHERE / date filters before aggregating |
| Unbounded external JSON | Validate member count via trigger or function |
| Full row serialization | Use row_to_json() instead of manual key listing |
Prevention Tips
1. Prefer arrays over flat key explosion.
Instead of creating one key per date or entity, use jsonb_agg() to group related data into arrays. This keeps object member counts low and your schema predictable.
-- Instead of: {"2024-01-01": 10, "2024-01-02": 20, ...}
-- Do this:
SELECT jsonb_build_object(
'year', 2024,
'entries', jsonb_agg(
jsonb_build_object('date', sale_date, 'amount', amount)
)
)
FROM daily_sales
GROUP BY EXTRACT(YEAR FROM sale_date);
2. Monitor JSON column sizes regularly.
Schedule a periodic query to track the distribution of JSON member counts across your tables. Catch growth trends before they cause production errors.
SELECT
MAX(cnt) AS max_members,
AVG(cnt)::NUMERIC(10,1) AS avg_members,
PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY cnt) AS p99_members
FROM (
SELECT COUNT(*) AS cnt
FROM your_table t,
jsonb_object_keys(t.json_col)
GROUP BY t.id
) s;
Related Errors
-
22032
invalid_json_text— Malformed JSON string that fails parsing entirely. -
22033
invalid_json_scope— Invalid scope in a JSON path expression. -
54000
program_limit_exceeded— Parent category for PostgreSQL internal resource limits, which can co-occur with JSON-related overflows.
📖 Want a more detailed guide?
Check out the full in-depth version (Korean) on oraerror.com — includes detailed analysis, additional SQL examples, and prevention tips.
Top comments (0)