JSON functions
Query JSON held in text columns. A path is one or more segments—string object keys or integer array indices—and each segment is a separate argument: the value at user.name is json_get_str(data, 'user', 'name'). The -> and ->> operators are shorthands for json_get and json_as_text; chain them to descend (data -> 'user' ->> 'name'). Casting a -> result, e.g. (data -> 'age')::int, rewrites to the typed getter.
Two things the signatures do not show:
- JSONPath is not supported.
json_get_str(data, '$.user.name')returns NULL rather than an error, so a JSONPath string reads as a missing key. .is not a path separator.data ->> 'a.b'looks up a key named literallya.b, which is why it finds the value in{"a.b": 1}and returns NULL on{"a": {"b": 1}}.
-- The two spellings that reach a nested value, and the two that look like they should.
> SELECT data -> 'user' ->> 'name' AS chained,
json_as_text(data, 'user', 'name') AS variadic,
json_get_str(data, '$.user.name') AS jsonpath,
data ->> 'user.name' AS dotted
FROM VALUES ('{"user": {"name": "ada", "age": 36}}') t(data);
+---------+----------+----------+--------+
| chained | variadic | jsonpath | dotted |
+---------+----------+----------+--------+
| ada | ada | | |
+---------+----------+----------+--------+
1 row(s) fetched.
Every example below queries the same document:
{"user": {"name": "ada", "age": 36, "admin": true, "score": 9.5}, "tags": ["sql", "json"]}
json_as_text
Value at a path, rendered as text: strings unquoted, everything else as its JSON text. Operator: json ->> key.
json_as_text(json, path...)
> SELECT data ->> 'tags' AS tags, data -> 'user' ->> 'name' AS name, json_as_text(data, 'user', 'age') AS age
FROM VALUES ('{"user": {"name": "ada", "age": 36, "admin": true, "score": 9.5}, "tags": ["sql", "json"]}') t(data);
+-----------------+------+-----+
| tags | name | age |
+-----------------+------+-----+
| ["sql", "json"] | ada | 36 |
+-----------------+------+-----+
1 row(s) fetched.
json_contains
Whether a value exists at the given path (boolean). There is no operator form—? is reserved as a parameter placeholder and does not parse.
json_contains(json, path...)
> SELECT json_contains(data, 'user') AS has_user, json_contains(data, 'user', 'name') AS has_name, json_contains(data, 'user', 'email') AS has_email
FROM VALUES ('{"user": {"name": "ada", "age": 36, "admin": true, "score": 9.5}, "tags": ["sql", "json"]}') t(data);
+----------+----------+-----------+
| has_user | has_name | has_email |
+----------+----------+-----------+
| true | true | false |
+----------+----------+-----------+
1 row(s) fetched.
json_get
JSON value at a path. Objects come back re-serialized with keys sorted; use json_get_json to keep the source text. Operator: json -> key.
json_get(json, path...)
> SELECT json_get(data, 'user', 'name') AS name, json_get(data, 'tags', 0) AS first_tag
FROM VALUES ('{"user": {"name": "ada", "age": 36, "admin": true, "score": 9.5}, "tags": ["sql", "json"]}') t(data);
+------+-----------+
| name | first_tag |
+------+-----------+
| ada | sql |
+------+-----------+
1 row(s) fetched.
json_get_array
Value at a path as an array. Each element is its own raw JSON text, so strings keep their quotes.
json_get_array(json, path...)
> SELECT json_get_array(data, 'tags') AS tags
FROM VALUES ('{"user": {"name": "ada", "age": 36, "admin": true, "score": 9.5}, "tags": ["sql", "json"]}') t(data);
+-------------------------+
| tags |
+-------------------------+
| ["\"sql\"", "\"json\""] |
+-------------------------+
1 row(s) fetched.
json_get_bool
Value at a path as a boolean.
json_get_bool(json, path...)
> SELECT json_get_bool(data, 'user', 'admin') AS admin
FROM VALUES ('{"user": {"name": "ada", "age": 36, "admin": true, "score": 9.5}, "tags": ["sql", "json"]}') t(data);
+-------+
| admin |
+-------+
| true |
+-------+
1 row(s) fetched.
json_get_float
Value at a path as a float.
json_get_float(json, path...)
> SELECT json_get_float(data, 'user', 'score') AS score
FROM VALUES ('{"user": {"name": "ada", "age": 36, "admin": true, "score": 9.5}, "tags": ["sql", "json"]}') t(data);
+-------+
| score |
+-------+
| 9.5 |
+-------+
1 row(s) fetched.
json_get_int
Value at a path as an integer. Casting a -> chain reaches the same value.
json_get_int(json, path...)
> SELECT json_get_int(data, 'user', 'age') AS age, (data -> 'user' -> 'age')::int AS age_cast
FROM VALUES ('{"user": {"name": "ada", "age": 36, "admin": true, "score": 9.5}, "tags": ["sql", "json"]}') t(data);
+-----+----------+
| age | age_cast |
+-----+----------+
| 36 | 36 |
+-----+----------+
1 row(s) fetched.
json_get_json
Nested raw JSON string at a path, exactly as it appears in the source document.
json_get_json(json, path...)
> SELECT json_get_json(data, 'user') AS user_json
FROM VALUES ('{"user": {"name": "ada", "age": 36, "admin": true, "score": 9.5}, "tags": ["sql", "json"]}') t(data);
+---------------------------------------------------------+
| user_json |
+---------------------------------------------------------+
| {"name": "ada", "age": 36, "admin": true, "score": 9.5} |
+---------------------------------------------------------+
1 row(s) fetched.
json_get_str
Value at a path as a string. NULL when the value is not a JSON string, so a number stays out of reach here—use json_get_int or json_as_text.
json_get_str(json, path...)
> SELECT json_get_str(data, 'user', 'name') AS name, json_get_str(data, 'user', 'age') AS age
FROM VALUES ('{"user": {"name": "ada", "age": 36, "admin": true, "score": 9.5}, "tags": ["sql", "json"]}') t(data);
+------+-----+
| name | age |
+------+-----+
| ada | |
+------+-----+
1 row(s) fetched.
json_length
Length of the array or object at a path—elements for an array, keys for an object. Alias: json_len.
json_length(json, path...)
> SELECT json_length(data, 'tags') AS tag_count, json_length(data, 'user') AS user_keys
FROM VALUES ('{"user": {"name": "ada", "age": 36, "admin": true, "score": 9.5}, "tags": ["sql", "json"]}') t(data);
+-----------+-----------+
| tag_count | user_keys |
+-----------+-----------+
| 2 | 4 |
+-----------+-----------+
1 row(s) fetched.
json_object_keys
Keys of the object at a path, as an array, in document order. Alias: json_keys.
json_object_keys(json, path...)
> SELECT json_object_keys(data, 'user') AS user_keys
FROM VALUES ('{"user": {"name": "ada", "age": 36, "admin": true, "score": 9.5}, "tags": ["sql", "json"]}') t(data);
+-----------------------------------+
| user_keys |
+-----------------------------------+
| ["name", "age", "admin", "score"] |
+-----------------------------------+
1 row(s) fetched.