Modify Nested Arrays in PostgreSQL JSON/B Columns
Postgres does not provide modification operations for nested JSON arrays out-of-the-box. By combining some JSON utility functions with array functions you can achieve dynamic update statements to do adding/removing by value.
Example
Let's create a simple user table with an id, username and metadata attribute which is of type jsonb:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE users (
id UUID NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(),
username TEXT NOT NULL,
meta_data JSONB NOT NULL DEFAULT '{}'::jsonb
);
INSERT INTO users (username, meta_data)
VALUES ('johndoe', '{"roles": ["hr", "marketing", "sales"]}'::jsonb);
SELECT id, username, meta_data
FROM users;
Which will print:
d9764861-f555-4e77-8f56-6c15b0d48136,johndoe,"{""roles"": [""hr"", ""marketing"", ""sales""]}"
Adding Elements
Let's add the "development" and "administration" roles to the users roles array:
UPDATE users
SET meta_data = jsonb_set(meta_data, ARRAY['roles'],
COALESCE(meta_data->'roles', '[]'::jsonb) || array_to_json(ARRAY['development', 'administration'])::jsonb)
WHERE id = 'd9764861-f555-4e77-8f56-6c15b0d48136';
Adding is rather simple. The function jsonb_set
takes as first parameter the json structure you want to update, as second parameter a PostgreSQL array of identifiers/indices leading to the part to update and as third parameter the new value. You could even address the first role of the user and replace it by a new value:
UPDATE users
SET meta_data = jsonb_set(meta_data, ARRAY['roles', 0], 'development')::jsonb)
WHERE id = 'd9764861-f555-4e77-8f56-6c15b0d48136';
Removing Element
Removing the two roles is a bit more complicate:
UPDATE users
SET meta_data = jsonb_set(meta_data, ARRAY['roles'],
array_to_json(
array_remove(
array(SELECT jsonb_array_elements_text(meta_data->'roles')),
'administration')
)::jsonb)
WHERE id = 'd9764861-f555-4e77-8f56-6c15b0d48136';
Postgres arrays offer a utility function to remove elements by value called array_remove(anyarray, text)
. Therefore we need to convert our JSON array to a Postgres type and then back. Converting to the Postgres type is more complicated. The function jsonb_array_elements_text
converts any JSON array to a result set which can be passed to the array()
constructor. Afterwards we can apply array_remove
and convert it back with array_to_json
.