Created
June 14, 2016 12:24
-
-
Save villelahdenvuo/5b1fbbf3f22853475bba14f8833faada to your computer and use it in GitHub Desktop.
PostgreSQL jsonb_set is nice, but it does't create missing objects so I made this little helper.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CREATE OR REPLACE FUNCTION jsonb_set_deep(target jsonb, path text[], val jsonb) | |
RETURNS jsonb AS $$ | |
DECLARE | |
k text; | |
p text[]; | |
BEGIN | |
-- Create missing objects in the path. | |
FOREACH k IN ARRAY path LOOP | |
p := p || k; | |
IF (target #> p IS NULL) THEN | |
target := jsonb_set(target, p, '{}'::jsonb); | |
END IF; | |
END LOOP; | |
-- Set the value like normal. | |
RETURN jsonb_set(target, path, val); | |
END; | |
$$ LANGUAGE plpgsql; |
Following @Venryx solution, I have created a function (in Kotlin) that creates this jsonb_set_deep statement:
https://gist.github.com/GuyKomari/d81ab763fc38bf88595f1cc29e5566ac
Version that handles conditions:
- if the target is initially null,
- if path is null (insert to root)
CREATE OR REPLACE FUNCTION jsonb_set_deep(target jsonb, path text[], val jsonb)
RETURNS jsonb AS $$
DECLARE
k text;
p text[];
BEGIN
IF (path = '{}') THEN
RETURN val;
ELSE
IF (target IS NULL) THEN
target = '{}'::jsonb;
END IF;
FOREACH k IN ARRAY path LOOP
p := p || k;
IF (target #> p IS NULL) THEN
target := jsonb_set(target, p, '{}'::jsonb);
ELSE
target := jsonb_set(target, p, target #> p);
END IF;
END LOOP;
-- Set the value like normal.
RETURN jsonb_set(target, path, val);
END IF;
END;
$$ LANGUAGE plpgsql;
It was pretty useful to include the ability to concatenate or merge objects (the ||
operator). Without that, the calling code is pretty verbose. This one should support arrays and act more like json_set. I'm not sure about older versions like pg9 but there are some short example SQLs included below.
/*
https://gist.github.com/villelahdenvuo/5b1fbbf3f22853475bba14f8833faada
Handles conditions:
* Accept array index in path
* Concatenation (using the || operator) when target path matches a type in the "concat" array parameter like ARRAY['array', 'object', 'string'] (array append, object merge, strings into an array of strings, etc.).
* If target is null, return null (like jsonb_set)
* If path is null return null (like jsonb_set)
* If path is empty return target (like jsonb_set)
* If concat is null return null
*/
CREATE FUNCTION jsonb_set_deep(
target jsonb, path text[], val jsonb, concat text[] = ARRAY['object']
)
RETURNS jsonb AS $$
DECLARE
k text;
p text[];
i int = 1;
BEGIN
IF target IS NULL OR path IS NULL OR concat IS NULL THEN
RETURN NULL; -- strict
END IF;
FOREACH k IN ARRAY path LOOP
p = p || k;
IF target #> p IS NULL THEN
IF path[i + 1] ~ '^[0-9]+$' THEN
target = jsonb_set(target, p, '[]'::jsonb);
ELSE
target = jsonb_set(target, p, '{}'::jsonb);
END IF;
END IF;
i = i + 1;
END LOOP;
IF jsonb_typeof(target #> path) = ANY(concat) THEN
RETURN jsonb_set(target, path, target #> path || val);
ELSE
RETURN jsonb_set(target, path, val);
END IF;
END;
$$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE;
/*
Accept array index in path
concat is true: {"a":{"b":1},"c":[1,2,{"d":3}]}
concat is false: {"a":{"b":1},"c":[2,{"d":3}]}
*/
SELECT jsonb_set_deep(
'{"a": {"b": 1}, "c":[1]}'::jsonb, ARRAY['c']::TEXT[],
'[2, {"d": 3}]'::jsonb,
ARRAY['object', 'array']
);
-- {"a": {"b": 1}, "c": ["abc", "def"]}
SELECT jsonb_set_deep(
'{"a": {"b": 1}, "c": "abc"}'::jsonb,
ARRAY['c']::TEXT[],
'"def"'::jsonb,
ARRAY['string']
);
/*
If target is null, return null (like jsonb_set)
?column?|?column?|
--------+--------+
true |true |
*/
SELECT jsonb_set(null, array['a']::text[], '1'::jsonb) IS NULL,
jsonb_set_deep(null, array['a']::text[], '1'::jsonb) IS NULL;
/*
If path is null return null (like jsonb_set)
?column?|?column?|
--------+--------+
true |true |
*/
SELECT jsonb_set('{}'::jsonb, null::text[], '1'::jsonb) IS NULL,
jsonb_set_deep('{}'::jsonb, null::text[], '1'::jsonb) IS NULL;
/*
If path is empty return target (like jsonb_set)
jsonb_set|jsonb_set_deep|
---------+--------------+
{"a": 1} |{"a": 1} |
*/
SELECT jsonb_set('{"a": 1}'::jsonb, array[]::text[], '{"a": 2}'::jsonb),
jsonb_set_deep('{"a": 1}'::jsonb, array[]::text[], '{"a": 2}'::jsonb);
/*
If concat is null return null
?column?|
--------+
true |
*/
SELECT jsonb_set_deep('{}'::jsonb, array['a']::text[], '1'::jsonb, NULL) IS NULL;
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For those who may dislike the deeply-nested nature of the calls above, here is an alternative: