JSONB PostgreSQL's Secret Weapon for Flexible Data

January 1, 2024

                                                                           

JSONB: PostgreSQL’s Secret Weapon for Flexible Data Modeling

Have you ever stared at your database schema and thought, “If I have to add one more column to this table, I’m going to lose it”? As you gaze into the abyss, wondering why you didn’t become a dentist, consider this: PostgreSQL can serve as both a document database and a relational database. We’ve all been there—trapped in migration hell, where each new business requirement sends us back to the drawing board. Maybe the requirements are a bit loose, and you’d rather not spend countless hours on schema migrations.

ChatGPT Image May 3, 2025, 11_15_56 PM.png

Add another column here, another table there, and suddenly your once-elegant database resembles a plate of spaghetti, with database patch scripts larger than your codebase.

But what if you could have the best of both worlds? The rock-solid reliability of PostgreSQL combined with the schema flexibility of a document database?

PostgreSQL’s JSONB is the database superpower you’ve been overlooking.

Why JSONB Matters to Modern Developers

Imagine you’re building a marketplace app. Shoes need sizes and materials. Electronics need compatibility specs. Food items need nutritional information. Each product category demands its own unique attributes.

The traditional approach would have you creating specialized tables for each product type or adding countless nullable columns to a monolithic products table. Both options lead to the same place: complexity and technical debt.

JSONB offers a different path. It lets you store rich, nested, schema-less data right alongside your traditional columns. Your core product attributes stay in regular columns, while the flexible stuff lives in JSONB—giving you the perfect hybrid model.

JSONB vs. JSON: What’s the Difference?

PostgreSQL actually offers two JSON types:

  • JSON: Stores the raw text exactly as entered, preserving whitespace and key order. It’s like putting your JSON in a lockbox—quick to store, but slow to search.
  • JSONB: Stores data in a binary format, removing whitespace, sorting keys, and enabling fast queries and indexing. Think of it as a searchable library rather than a locked box.

For almost all use cases, JSONB is the clear winner. Let’s see why with some examples.

Getting Started with JSONB

Creating a table with JSONB is straightforward:

CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  name TEXT,
  price DECIMAL(10, 2),
  attributes JSONB
);

Here, id, name, and price are regular columns for the data that’s consistent across all products, while attributes can store any JSON structure you need.

Let’s insert a product:

INSERT INTO products (
  name,
  price,
  attributes
)
VALUES (
  'Running Shoes',
  89.99,
  '{
    "sizes": [7, 8, 9, 10],
    "color": "blue",
    "features": {
      "waterproof": true,
      "cushioning": "high"
    }
  }'
);

Notice how attributes can handle nested structures, arrays, and mixed data types—all in a single column.

Powerful Querying Capabilities

JSONB truly shines when it comes to querying. PostgreSQL provides specialized operators for working with JSON data:

  • >: Gets a JSON object field or array element
  • »: Gets a JSON value as text
  • @>: Checks if a JSON document contains another JSON document

Basic Field Access

Want to see all product colors?

SELECT name, attributes->>'color' AS color
FROM products;

Array Queries

Need all products available in size 9?

SELECT name
FROM products
WHERE attributes @> '{"sizes": [9]}';

Nested Field Filtering

Looking for all waterproof products?

SELECT name
FROM products
WHERE attributes->'features'->>'waterproof' = 'true';

These operators give you remarkable flexibility for querying complex data structures—something that would typically require multiple joins in a purely relational model.

Updating JSONB: More Than Meets the Eye

JSONB isn’t just read-only. PostgreSQL provides several methods for updating JSON data:

Field Updates with jsonb_set()

Want to change a product’s color?

UPDATE products
SET attributes = jsonb_set(
      attributes, '{color}', '"red"')
WHERE name = 'Running Shoes';

The jsonb_set() function takes the existing JSONB object, a path to the field you want to change, and the new value.

Adding New Fields with the Merge Operator (||)

Need to add a new field to existing records?

UPDATE products
SET attributes = attributes || '{"on_sale": true}'
WHERE name = 'Running Shoes';

The || operator merges two JSONB objects, adding or replacing fields as needed.

Working with Arrays

To add a new size option:

UPDATE products
SET attributes = jsonb_set(
      attributes,
      '{sizes}',
      (attributes->'sizes') || '11'::jsonb
)
WHERE name = 'Running Shoes';

This adds size 11 to the existing sizes array.

Indexing JSONB for Performance

As your data grows, you’ll need proper indexing to maintain fast queries. PostgreSQL offers several index types for JSONB:

GIN Index for Full Document Queries

The GIN (Generalized Inverted Index) is perfect for containment queries like the @> operator:

CREATE INDEX idx_products_attributes_gin
ON products USING GIN (attributes);

This index makes queries like WHERE attributes @> '{"color": "blue"}' lightning fast, even with millions of records.

Expression Indexes for Specific Fields

If you frequently filter on a specific field, an expression index can help:

CREATE INDEX idx_products_color
ON products ((attributes->>'color'));

This creates an index only on the color field, making queries like WHERE attributes->>'color' = 'blue' much faster.

Partial Indexes for Subsets

For even more targeted indexing, you can create a partial index:

CREATE INDEX idx_products_on_sale
ON products ((attributes->>'on_sale'))
WHERE attributes @> '{"on_sale": true}';

This creates an index only for products that are on sale, keeping the index small and efficient.

Working with JSONB in Python

The real power of JSONB shines when you combine it with your application code. Modern PostgreSQL drivers like psycopg3 and asyncpg handle the conversion between Python dictionaries and JSONB seamlessly:

import psycopg


# Define product attributes
product_attrs = {
    "sizes": [7, 8, 9],
    "color": "blue",
    "features": {
        "waterproof": True,
        "cushioning": "high"
    }
}


# Connect to database
with psycopg.connect(
    "dbname=shop "
    "user=postgres "
    "password=secret",
    autocommit=True
) as conn:
    with conn.cursor() as cur:
        # Insert data
        cur.execute(
            """
            INSERT INTO products 
            (name, price, attributes)
            VALUES (%s, %s, %s)
            """,
            (
                "Trail Runners",
                129.99,
                product_attrs
            )
        )

        # Fetch data
        cur.execute(
            """
            SELECT attributes 
            FROM products
            WHERE name = %s
            """,
            ("Trail Runners",)
        )
        
        # Process results
        row = cur.fetchone()
        attrs = row[0]

        # Access data
        print(attrs["color"])  # blue
        print(
            attrs["features"]["waterproof"]
        )  # True

The beauty here is that you’re working with native Python data structures on both ends. The conversion to and from JSONB happens transparently.

Best Practices for JSONB

While JSONB provides incredible flexibility, it’s important to use it wisely:

  1. Hybrid Modeling: Use regular columns for stable, frequently queried data. Use JSONB for flexible, evolving data.
  2. Schema Validation: Just because JSONB doesn’t enforce a schema doesn’t mean you shouldn’t have one. Use tools like Pydantic or JSON Schema to validate data before insertion:
from pydantic import BaseModel, ValidationError

class ProductAttributes(BaseModel):
    sizes: list[int]
    color: str
    features: dict

try:
    attrs = ProductAttributes(**product_attrs)
    # Now safe to insert into the database
except ValidationError as e:
    print(e.json())
  1. Consistent Keys: Maintain consistent key names and types across documents to make querying easier.
  2. Index Strategically: Don’t over-index. Use EXPLAIN to verify your queries are using the intended indexes.
  3. Consider Denormalization: JSONB makes it easy to embed related data, reducing joins. For example, you might include category information directly in the product document.

Real-World Use Cases for JSONB

JSONB isn’t just theoretical—it’s solving real problems today:

  1. E-commerce Product Catalogs: Store varying product attributes without schema changes.
  2. User Preferences and Settings: Keep all user settings in a single JSONB column, easily extendable without migrations.
  3. Event Sourcing and Logging: Capture events with flexible payloads that can evolve over time.
  4. API Response Caching: Store complex API responses directly in PostgreSQL. 5.Feature Flags and A/B Testing: Use JSONB to store configuration parameters for features.

When Not to Use JSONB

JSONB isn’t a silver bullet. Consider these limitations:

1.Fixed Schema Data: If your data structure is truly fixed and won’t change, regular columns are simpler and slightly more efficient. 2.Heavy Transactional Workloads: Updating small parts of very large JSONB documents can be less efficient than updating individual columns. 3.Binary Data: JSONB is not suitable for storing binary data—use bytea instead.

Conclusion: The Best of Both Worlds

PostgreSQL’s JSONB type bridges the gap between traditional relational databases and document stores. It gives you the rock-solid reliability and transactional guarantees of PostgreSQL with the flexibility of a document database.

With JSONB, you can:

  • Avoid endless schema migrations
  • Model complex, nested data naturally
  • Query and index flexibly structured data efficiently
  • Build applications that adapt easily to changing requirements

The next time you find yourself facing complex, evolving data structures, remember that PostgreSQL has your back. JSONB isn’t just a feature—it’s a superpower that might just change how you think about data modeling altogether.


Have you used JSONB in your projects? What challenges did it help you solve? Share your experiences in the comments below!

About the AuthorRick Hightower is a seasoned software architect and database expert with extensive experience in enterprise application development. With a deep understanding of PostgreSQL and modern data modeling techniques, Rick helps teams build scalable, flexible systems that stand the test of time. When not writing about databases, he can be found mentoring fellow developers and speaking at technical conferences.

He maintains an active blog where he shares in-depth technical insights and practical tips for working with modern database technologies.

image.png

                                                                           
comments powered by Disqus

Apache Spark Training
Kafka Tutorial
Akka Consulting
Cassandra Training
AWS Cassandra Database Support
Kafka Support Pricing
Cassandra Database Support Pricing
Non-stop Cassandra
Watchdog
Advantages of using Cloudurable™
Cassandra Consulting
Cloudurable™| Guide to AWS Cassandra Deploy
Cloudurable™| AWS Cassandra Guidelines and Notes
Free guide to deploying Cassandra on AWS
Kafka Training
Kafka Consulting
DynamoDB Training
DynamoDB Consulting
Kinesis Training
Kinesis Consulting
Kafka Tutorial PDF
Kubernetes Security Training
Redis Consulting
Redis Training
ElasticSearch / ELK Consulting
ElasticSearch Training
InfluxDB/TICK Training TICK Consulting