{"post":{"slug":"json-columns-explained-2026","title":"JSON Columns: Flexible Schema With Real Tradeoffs","excerpt":"Storing a document in a column avoids schema migrations for every new field. It also gives up type checking, constraint enforcement, and straightforward indexing.","category":"code","date":"January 5, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">Y</span>ou have data whose shape varies — user preferences, event payloads, third-party API responses, product attributes that differ by category. Modelling every possible field as a column means a wide sparse table and a migration whenever something new appears.\n\nA JSON column holds the whole document. It solves that problem and hands back several others.\n\n## What changed in 2026\n\n- **Binary JSON became the default.** Text-based JSON storage largely disappeared from new designs.\n- **Generated columns became the standard companion.** Extracting queried fields into real indexable columns emerged as best practice — see [generated columns](/blog/generated-columns-explained-2026).\n- **Indexing improved.** Better support for indexing inside documents narrowed the gap with real columns.\n- **The hybrid model settled.** Structured columns for known fields, JSON for the variable remainder, became the standard shape rather than choosing one.\n\n## Binary, not text\n\nThe first decision, and it is not close.\n\nText JSON stores the document as a string. Every read parses it, whitespace and key order are preserved pointlessly, and indexing inside it is not possible.\n\nBinary JSON parses once on write and stores a structured representation. Reads extract fields without parsing the whole document, and the format supports indexing inside it.\n\nThe only reason to store text is if you need byte-exact preservation of the original document — an audit requirement, or a signature over the exact bytes. Otherwise binary, always.\n\n## What you give up\n\n| | Real columns | JSON column |\n| --- | --- | --- |\n| Type enforcement | Yes | No |\n| NOT NULL, CHECK constraints | Yes | Awkward at best |\n| Foreign keys | Yes | No |\n| Indexing | Straightforward | Possible, more work |\n| Query planner statistics | Good | Weaker |\n| Schema visible in the database | Yes | No |\n| Adding a field | Migration | Nothing |\n\nThe constraint row is the one that causes real problems over time. Nothing stops a string appearing where every other row has a number, a required field being absent, or a typo in a key name creating a parallel field nobody notices. The database will not tell you; a report will, months later.\n\nThe statistics row matters for performance. The planner has good information about column distributions and much weaker information about what is inside a document, which produces worse row estimates and therefore worse plans — see [query planners](/blog/query-planner-explained-2026).\n\n## The hybrid that works\n\nThe design most teams converge on: put the fields you *query, filter, or join on* in real columns, and the variable remainder in JSON.\n\nGenerated columns make this clean. Define a stored generated column extracting a field from the document, and you get a real, typed, indexable column that stays automatically in sync with the JSON. No application code maintains it, no drift is possible.\n\nThat gives you indexed queries on the fields that matter, type checking where you extracted, and schema flexibility for everything else — see [generated columns](/blog/generated-columns-explained-2026).\n\nWhere you must query inside the document directly, index it. Engines offer index types designed for containment and existence queries within documents, which is far better than scanning — see [GIN indexes](/blog/gin-indexes-explained-2026).\n\n## The out-of-line storage surprise\n\nLarge values are typically stored outside the main table row, in separate storage, and fetched when accessed. That is transparent and it changes performance in ways people do not anticipate.\n\nA table whose rows contain large JSON documents may perform well on queries that do not touch the JSON, because those documents are not in the main table pages being scanned. Then a query selecting the document becomes dramatically slower, because each row now requires an additional fetch.\n\nThis is why `SELECT *` on a table with large JSON columns can be far slower than selecting the columns you need — the difference is not marginal — see [TOAST storage](/blog/toast-storage-explained-2026).\n\n## Common mistakes\n\n- **Text instead of binary storage.** Parsing on every read, no indexing.\n- **JSON for stable known shapes.** Gives up everything for no benefit.\n- **No generated columns for queried fields.** Slow, unindexed filtering.\n- **Assuming the shape is consistent.** Nothing enforces it.\n- **`SELECT *` with large documents.** Fetches out-of-line data unnecessarily.\n- **Deep nesting.** Awkward to query and index.\n- **No validation on write.** The application becomes the only schema, and it drifts.\n\n## FAQ\n\n**Should I validate JSON on write?**\nYes, in the application or with a check constraint. Without validation the column accumulates variations nobody knows about, and discovering them later is expensive.\n\n**How do I index inside a document?**\nEither extract the field to a generated column and index that, which is best for specific fields, or use a document-aware index type for containment queries across many keys.\n\n**Is a document database better?**\nIf nearly everything is documents and you rarely need joins or constraints, possibly. If most of your data is relational with some variable parts, a JSON column keeps one system rather than two.\n\n**What about querying arrays inside documents?**\nSupported and less efficient than a proper join table. If you frequently query by array membership, a separate table is usually the better model.\n\n## Where to go next\n\nFor extracting fields into indexable columns, read [generated columns](/blog/generated-columns-explained-2026). For indexing inside documents, [GIN indexes](/blog/gin-indexes-explained-2026), and for the out-of-line storage behaviour, [TOAST storage](/blog/toast-storage-explained-2026).","hue":305,"metaTitle":"JSON Columns: Flexible Schema With Real Tradeoffs","metaDescription":"When to use JSON columns in a relational database, why binary storage matters, and how generated columns recover indexing and type safety.","keywords":["json columns","jsonb postgres","semi structured data","schemaless relational","generated column extraction","json indexing","document in database","database design 2026"],"tldr":["**Use the binary JSON type, not text** — text storage means parsing on every read and no indexing.","**Extract fields you query into real columns** — generated columns give you both flexibility and indexes.","**You lose constraints and type safety** — nothing stops a string appearing where a number belonged.","**Large documents get stored out of line** — which changes read cost in ways that surprise people.","**Skip:** JSON columns for data with a known stable shape. Columns are better in every respect there."],"readTime":"4 min read","wordCount":889},"related":[{"slug":"generated-columns-explained-2026","title":"Generated Columns: Let the Database Compute It","excerpt":"A value derived from other columns can be maintained by the database itself, indexed like any other column, and never fall out of sync with its inputs.","category":"code","date":"August 15, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":304},{"slug":"how-to-design-a-database-schema-2026","title":"How to design a database schema in 2026: structure, constraints, and tradeoffs","excerpt":"A schema designed under pressure becomes a schema you refactor for years. This guide covers relational design principles, naming conventions, and 2026 tradeoffs.","category":"code","date":"March 30, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":312},{"slug":"what-is-a-database-schema-2026","title":"What Is a Database Schema in 2026: the blueprint for data","excerpt":"A database schema is the blueprint that defines how data is organized: the tables, columns, types, and relationships. Here is what a schema is and why it matters.","category":"code","date":"June 22, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":222}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"installment-sales-explained-2026","title":"Installment Sales: Spreading Gain Across Years","excerpt":"Taking payment over several years spreads the gain across those years, which can keep you in lower brackets. Depreciation recapture does not cooperate, and neither does buyer default.","category":"finance","date":"January 5, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":280},"older":{"slug":"metadata-privacy-explained-2026","title":"Metadata Privacy: What Your Files Say Without the Contents","excerpt":"A photograph records where and when it was taken and on what device. A document records who wrote it and every edit. That information travels with the file unless you remove it.","category":"guides","date":"January 5, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":168}}