A customer recently asked a great question about working with jsonb attributes in YugabyteDB:
1. Are JSONB attribute values always returned as text?
2. MongoDB users might view the need for casting as cumbersome.
3. Are there techniques to lessen the amount of code or casting?
This is a very reasonable question, especially for users coming from MongoDB.
MongoDB documents can naturally store polymorphic values, and if you insert a numeric value, MongoDB aggregation operations can generally treat that value as numeric. In SQL, the rules are more explicit. A JSONB document can contain a JSON number, but SQL aggregate functions like AVG(), SUM(), and arithmetic expressions operate on SQL types such as integer, numeric, or double precision.
The good news is that YugabyteDB gives you several techniques to make this cleaner.
The first step is understanding the difference between the JSONB operators.
-> vs. ->>
If you use ->>, the value is returned as text.
If you use ->, the value is returned as jsonb.
Here is a simple example:
SELECT
'{"WhatAMI?":1}'::jsonb AS some_jsonb,
'{"WhatAMI?":1}'::jsonb->'WhatAMI?' AS value_as_jsonb,
'{"WhatAMI?":1}'::jsonb->>'WhatAMI?' AS value_as_text;
That works, but yes, repeating the cast everywhere can feel a little noisy.
Fortunately, you have several options to reduce that repetition.
Technique 1: Use a View to Hide the Cast
One simple way to reduce repeated casting is to create a view that exposes commonly used JSONB attributes as typed SQL columns.
CREATE VIEW orders_jsonb_v AS
SELECT
id,
payload,
payload->>'customer' AS customer,
(payload->>'amount')::numeric AS amount,
payload->>'status' AS status
FROM orders_jsonb;
Now your query becomes much cleaner:
SELECT avg(amount) AS avg_amount
FROM orders_jsonb_v;
You can also group by status naturally:
SELECT
status,
count(*) AS order_count,
avg(amount) AS avg_amount
FROM orders_jsonb_v
GROUP BY status
ORDER BY status;
🛠️ Practical Pattern
If your application stores flexible JSONB documents but frequently queries the same attributes, create a view that exposes those attributes as normal typed SQL columns.
Technique 2: Add a Check Constraint for Type Safety
If the application expects amount to always be numeric, you can enforce that at the database layer.
SELECT avg(amount) AS avg_amount
FROM orders_jsonb_generated;
You can also index the generated column:
CREATE INDEX orders_jsonb_generated_amount_idx
ON orders_jsonb_generated (amount ASC);
⚙️ Design Tip
JSONB is great for flexible attributes, but if an attribute becomes important for filtering, joining, sorting, or aggregation, consider promoting it to a typed column, generated column, or view expression.
Technique 4: Use a Range-Sharded Expression Index
If you commonly filter on a JSONB numeric attribute using range predicates, create an expression index and specify ASC or DESC.
CREATE INDEX orders_jsonb_amount_expr_idx
ON orders_jsonb (((payload->>'amount')::numeric) ASC);
ANALYZE orders_jsonb;
The ASC matters in YugabyteDB. Without it, the first index key may default to HASH, which is useful for equality lookups but not ideal for range predicates like >=, <, or BETWEEN.
Now the range query can use the expression index:
EXPLAIN
SELECT *
FROM orders_jsonb
WHERE (payload->>'amount')::numeric >= 995;
Example plan:
. QUERY PLAN
-------------------------------------------------------------------------------------------------------
Index Scan using orders_jsonb_amount_expr_idx on orders_jsonb (cost=56.82..217.52 rows=525 width=86)
Index Cond: (((payload ->> 'amount'::text))::numeric >= '995'::numeric)
(2 rows)
MongoDB Perspective
MongoDB users are used to inserting flexible documents like this:
{ "amount": 19.95 }
And then using aggregation pipelines that understand numeric BSON values.
YugabyteDB’s YSQL layer follows PostgreSQL-compatible SQL semantics. A JSONB document can contain a JSON number, but when you want SQL functions like AVG(), SUM(), comparisons, joins, or indexes to operate on that value, you should expose it as a SQL type.
That can feel like extra code at first, but it also gives you more explicit control.
You can decide where the conversion belongs.
Technique
Best For
->
Keeping the value as jsonb
->>
Extracting a scalar value as text
Explicit casts
Ad hoc queries and simple expressions
Views
Hiding repeated JSONB extraction and casting logic
Check constraints
Enforcing expected JSONB attribute types
Generated columns
Frequently queried JSONB attributes that should behave like typed columns
Expression indexes
Indexing typed values extracted from JSONB
Final Takeaway
JSONB attribute values are not always returned as text. It depends on the operator:
● Use -> when you want the result as jsonb.
● Use ->> when you want the result as text.
For SQL aggregation, filtering, sorting, and indexing, you usually want to cast JSONB scalar values into proper SQL types. To reduce repeated code, centralize that logic with views, generated columns, check constraints, or expression indexes.
For YugabyteDB specifically, remember that index sharding matters. If you create an expression index to support range predicates such as >=, <, or BETWEEN, define the indexed expression with ASC or DESC.
For example:
CREATE INDEX orders_jsonb_amount_expr_idx
ON orders_jsonb (((payload->>'amount')::numeric) ASC);
Without ASC or DESC, the first index key may use hash sharding by default, which is better suited for equality lookups than range scans.
🚀 Final Thought
YugabyteDB gives you flexible JSONB storage and relational SQL power. Use JSONB for flexibility, promote important attributes into typed SQL expressions when needed, and choose the right index ordering for your access pattern. For range queries on JSONB-derived values, ASC or DESC can make the difference between a sequential scan and an efficient index scan.