How to turn JSON array into Postgres array?
Postgres 9.4 or newer
Obviously inspired by this post, Postgres 9.4 added the missing function(s):
Thanks to Laurence Rowe for the patch and Andrew Dunstan for committing!
json_array_elements_text(json)
jsonb_array_elements_text(jsonb)
To unnest the JSON array. Then use array_agg()
or an ARRAY constructor to build a Postgres array from it. Or string_agg()
to build a text
string.
Aggregate unnested elements per row in a LATERAL
or correlated subquery. Then original order is preserved and we don't need ORDER BY
, GROUP BY
or even a unique key in the outer query. See:
- How to apply ORDER BY and LIMIT in combination with an aggregate function?
Replace 'json' with 'jsonb' for jsonb
in all following SQL code.
SELECT t.tbl_id, d.list
FROM tbl t
CROSS JOIN LATERAL (
SELECT string_agg(d.elem::text, ', ') AS list
FROM json_array_elements_text(t.data->'tags') AS d(elem)
) d;
Short syntax:
SELECT t.tbl_id, d.list
FROM tbl t, LATERAL (
SELECT string_agg(value::text, ', ') AS list
FROM json_array_elements_text(t.data->'tags') -- col name default: "value"
) d;
Related:
- What is the difference between LATERAL and a subquery in PostgreSQL?
ARRAY constructor in correlated subquery:
SELECT tbl_id, ARRAY(SELECT json_array_elements_text(t.data->'tags')) AS txt_arr
FROM tbl t;
Related:
- How to apply ORDER BY and LIMIT in combination with an aggregate function?
Subtle difference: null
elements are preserved in actual arrays. This is not possible in the above queries producing a text
string, which cannot contain null
values. The true representation is an array.
Function wrapper
For repeated use, to make this even simpler, encapsulate the logic in a function:
CREATE OR REPLACE FUNCTION json_arr2text_arr(_js json)
RETURNS text[] LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
'SELECT ARRAY(SELECT json_array_elements_text(_js))';
Make it an SQL function, so it can be inlined in bigger queries.
Make it IMMUTABLE
(because it is) to avoid repeated evaluation in bigger queries and allow it in index expressions.
Make it PARALLEL SAFE
(in Postgres 9.6 or later!) to not stand in the way of parallelism. See:
- When to mark functions as PARALLEL RESTRICTED vs PARALLEL SAFE?
Call:
SELECT tbl_id, json_arr2text_arr(data->'tags')
FROM tbl;
db<>fiddle here
Postgres 9.3 or older
Use the function json_array_elements()
. But we get double quoted strings from it.
Alternative query with aggregation in the outer query. CROSS JOIN
removes rows with missing or empty arrays. May also be useful for processing elements. We need a unique key to aggregate:
SELECT t.tbl_id, string_agg(d.elem::text, ', ') AS list
FROM tbl t
CROSS JOIN LATERAL json_array_elements(t.data->'tags') AS d(elem)
GROUP BY t.tbl_id;
ARRAY constructor, still with quoted strings:
SELECT tbl_id, ARRAY(SELECT json_array_elements(t.data->'tags')) AS quoted_txt_arr
FROM tbl t;
Note that null
is converted to the text value "null", unlike above. Incorrect, strictly speaking, and potentially ambiguous.
Poor man's unquoting with trim()
:
SELECT t.tbl_id, string_agg(trim(d.elem::text, '"'), ', ') AS list
FROM tbl t, json_array_elements(t.data->'tags') d(elem)
GROUP BY 1;
Retrieve a single row from tbl:
SELECT string_agg(trim(d.elem::text, '"'), ', ') AS list
FROM tbl t, json_array_elements(t.data->'tags') d(elem)
WHERE t.tbl_id = 1;
Strings form correlated subquery:
SELECT tbl_id, (SELECT string_agg(trim(value::text, '"'), ', ')
FROM json_array_elements(t.data->'tags')) AS list
FROM tbl t;
ARRAY constructor:
SELECT tbl_id, ARRAY(SELECT trim(value::text, '"')
FROM json_array_elements(t.data->'tags')) AS txt_arr
FROM tbl t;
Original (outdated) SQL Fiddle.
db<>fiddle here.
Related:
- Need to select a JSON array element dynamically from a postgresql table
Notes (outdated since pg 9.4)
We would need a json_array_elements_text(json)
, the twin of json_array_elements(json)
to return proper text
values from a JSON array. But that seems to be missing from the provided arsenal of JSON functions. Or some other function to extract a text
value from a scalar JSON
value. I seem to be missing that one, too.
So I improvised with trim()
, but that will fail for non-trivial cases ...
PG 9.4+
The accepted answer is definitely what you need, but for the sake of simplicity here is a helper I use for this:
CREATE OR REPLACE FUNCTION jsonb_array_to_text_array(p_input jsonb)
RETURNS text[]
LANGUAGE sql
IMMUTABLE
AS $function$
SELECT array_agg(ary)::text[] FROM jsonb_array_elements_text(p_input) AS ary;
$function$;
Then just do:
SELECT jsonb_array_to_text_array('["a", "b", "c"]'::jsonb);
Updated 2/23/2020 in response to comments: Comments are correct that this could be more efficient. At the time I posted there was no modularized solution offered so I offered one in earnest, if non-optimal. Since then Erwin has updated his answer with a simple and efficient function so I never updated mine. Updating it now since there is still attention coming to this answer
One more update, because this just bit me:
The above function will return null
if there are no values. This may not be desirable depending on your situation. Here's a function which returns an empty array if the value is not null
, but still returns null if the input is null.
CREATE OR REPLACE FUNCTION jsonb_array_to_text_array_strict(p_input jsonb)
RETURNS text[]
LANGUAGE sql
IMMUTABLE
AS $function$
SELECT
CASE
WHEN p_input IS null
THEN null
ELSE coalesce(ary_out, ARRAY[]::text[])
END
FROM (
SELECT array_agg(ary)::text[] AS ary_out
FROM jsonb_array_elements_text(p_input) AS ary
) AS extracted;
$function$
;
This question was asked on the PostgreSQL mailing lists and I came up with this hackish way of converting JSON text to PostgreSQL text type via the JSON field extraction operator:
CREATE FUNCTION json_text(json) RETURNS text IMMUTABLE LANGUAGE sql
AS $$ SELECT ('['||$1||']')::json->>0 $$;
db=# select json_text(json_array_elements('["hello",1.3,"\u2603"]'));
json_text
-----------
hello
1.3
☃
Basically it converts the value into a single-element array and then asks for the first element.
Another approach would be to use this operator to extract all fields one-by-one. But for large arrays this is likely slower, as it needs to parse the whole JSON string for each array element, leading to O(n^2) complexity.
CREATE FUNCTION json_array_elements_text(json) RETURNS SETOF text IMMUTABLE LANGUAGE sql
AS $$ SELECT $1->>i FROM generate_series(0, json_array_length($1)-1) AS i $$;
db=# select json_array_elements_text('["hello",1.3,"\u2603"]');
json_array_elements_text
--------------------------
hello
1.3
☃