The JSON Column Anti-Pattern and When It Is Actually Fine
When is the JSON column an anti-pattern?
The JSON column is an anti-pattern when it stores data with a stable, known structure that belongs in typed columns, because it sacrifices query performance, data validation, and schema documentation to avoid the work of proper data modeling.
I found a “customer_details” JSON column that consistently contained the same 7 fields: name, email, phone, address, city, state, zip. Every single row had the same structure. The JSON column existed because the developer “wanted flexibility.” But there was no flexibility being used. There was a relational schema being avoided. Queries against this column required JSON extraction functions that were 4x slower than equivalent typed-column queries. No database-level validation ensured that required fields were present. The data modeling discipline had been skipped, and the system paid for it in performance and reliability.
When is the JSON column actually fine?
The JSON column is appropriate for genuinely variable data: event metadata where different event types have different fields, user-defined configurations, API response caching, and any scenario where the schema varies by row or changes frequently.
I use JSON columns in 3 legitimate patterns. First, event properties: a clickstream table where each event type (page_view, button_click, form_submit) has different metadata fields. Normalizing this into typed columns would require a new migration for every new event type. Second, configuration storage: application settings that vary by tenant and change without schema migrations. Third, API response caching: storing raw API responses that are parsed downstream but need to be preserved in original form for debugging. According to JSON’s design principles, it excels at representing semi-structured and variable data. The anti-pattern occurs when it is used for data that is not actually variable.
The guideline I apply: if you can predict the JSON structure for 90% of rows, it should probably be typed columns. If the structure genuinely varies by row or changes monthly, JSON is appropriate. The question is not “should I use JSON?” It is “is this data’s structure genuinely unpredictable, or am I avoiding schema design?” One answer justifies the column. The other is technical debt wearing a flexibility costume.