Found a bit more elegant and scalable solution and wanted to share.
Given: the following complex jsonb object inside tbl.data:
{
top: {
nested1: { leaf: 1 },
nested2: { flower1: 0, flower2: 0 },
nested4: { apple: false }
},
bottom: { roots: 'lots' }
}
Problem: need to change values of .top.nested1.leaf = 2 and .top.nested2.flower1 = 2 and even add something like .top.nested3.branch = 1 in one transaction.
Solution: This can be achieved the following way:
WITH tbl as (SELECT '{ "top": { "nested1": { "leaf": 1 }, "nested2": { "flower1": 0, "flower2": 0 }, "nested4": { "apple": false } }, "bottom":{ "roots": "lots" } }'::jsonb AS data)
SELECT jsonb_set(
data,
'{top}',
data->'top' || jsonb_build_object(
'nested1',
jsonb_set(data#>'{top,nested1}','{leaf}', to_jsonb(2)),
'nested2',
jsonb_set(data#>'{top,nested2}','{flower1}', to_jsonb(2)),
'nested3',
jsonb_set('{}','{branch}', to_jsonb(1))
)
) FROM tbl;
Result: only relevant fields changed.
{
top: {
nested1: { leaf: 2 },
nested2: { flower1: 2, flower2: 0 },
nested3: { branch: 1 },
nested4: { apple: false }
},
bottom: { roots: 'lots' }
}
Explanation: the keys here are two powerful yet still convenient operations: jsonb_build_object() and jsonb concatenation ||.
- top
jsonb_set() surgically replaces value of the top-most object field that is going to change with new jsonb value
- this new
jsonb value is constructed from the original value data->'top' by concatenating (||) it with new object that contains only fields that change with fully reconstructed values. This results in shallow replacement of the fields with same name and addition of fields that did not exist.
- since values of the new object are objects themselves, we use
jsonb_set() to fully extract those objects and change only related fields inside at once.
P.S.> It's probably worth moving this question to dba.stackexchange.com if possible.
CREATE TABLE test(data jsonb); INSERT INTO test(data) VALUES('{"top": {"nested": {"leaf" : 1, "paper": 10}, "other_nested": {"paper": 0, "leaf": 0}}}'::jsonb); UPDATE test SET data = data || '{"top": {"nested": {"leaf": 2}, "other_nested": {"paper": 2}}}'; SELECT data FROM test;by using the concatenation, which also did not work.