Querying JSONB in PostgreSQL
This does not work because it's trying to cast a jsonb value to integer.  

select data->'name' as name from persons where cast(data->'age' as int) > 25

This would actually work:

SELECT data->'name' AS name FROM persons WHERE cast(data->>'age' AS int) > 25;

Or shorter:

SELECT data-&gt;'name' AS name FROM persons WHERE (data-&gt;&gt;'age')::int &gt; 25;


And this:

SELECT data-&gt;'name' AS name FROM persons WHERE data-&gt;&gt;'name' &gt; 'Jenny';


Seems like confusion with the two operators -&gt; and -&gt;&gt; and operator precedence. The cast :: binds stronger than the json(b) operators.

Figure out type dynamically

This is the more interesting part of your question:


  the type of age in the JSON document is number anyway, so why can't PostgreSQL figure out that by itself?


SQL is a strictly typed language, it does not allow the same expression to evaluate to integer in one row and to text in the next. But since you are only interested in the boolean result of the test, you can get around this restriction with a CASE expression that forks depending on the result of jsonb_typeof():

SELECT data-&gt;'name'
FROM   persons
WHERE  CASE jsonb_typeof(data-&gt;'age')
        WHEN 'number'  THEN (data-&gt;&gt;'age')::numeric &gt; '25' -- treated as numeric
        WHEN 'string'  THEN data-&gt;&gt;'age' &gt; 'age_level_3'   -- treated as text
        WHEN 'boolean' THEN (data-&gt;&gt;'age')::bool           -- use boolean directly (example)
        ELSE FALSE                                         -- remaining: array, object, null
       END;


An untyped string literal to the right of the &gt; operator is coerced to the respective type of the value to the left automatically. If you put a typed value there, the type has to match or you have to cast it explicitly - unless there is adequate implicit cast registered in the system.

If you know that all numeric values are actually integer, you can also:

... (data-&gt;&gt;'age')::int &gt; 25 ...