I think this can be done using an custom aggregate and existing jsonb functionality:
The following aggregate will "merge" two jsonb values into one. Essentially the same as a || b
(where a and b are jsonb values) but as an aggregate for multiple rows.
create aggregate jsonb_merge(jsonb)
(
sfunc = jsonb_concat(jsonb, jsonb),
stype = jsonb
);
With that aggregate it's possible to write a recursive function that removes keys with null values:
create function strip_nulls(p_input jsonb)
returns jsonb
as
$$
select jsonb_aggregate(o)
from (
select jsonb_build_object(t.k, case jsonb_typeof(t.v) when 'object' then strip_nulls(t.v) else t.v end) o
from jsonb_each(p_input) as t(k,v)
where t.v::text <> 'null'
) t;
$$
language sql;
The inner select turns a jsonb value into a set of key/value pairs and removes the null
values recursively. Then the resulting values are aggregated back into a jsonb object.
select strip_nulls('{"one": null,
"two": 2,
"three": {"four": 1, "five": null,
"six": {"seven": 7, "eight": null}
}
}'::jsonb)
returns:
strip_nulls
-----------------------------------------------------
{"two": 2, "three": {"six": {"seven": 7}, "four": 1}}
The result can be cast back to json
I don't have a Postgres 9.4 installation around for testing, but I think I only used functions available in 9.4.
{"k1": 1, "k2": null, "k3": {"k31": 31, "k32": null}}