1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-16 06:01:02 +03:00

Allow json{b}_strip_nulls to remove null array elements

An additional paramater ("strip_in_arrays") is added to these functions.
It defaults to false. If true, then null array elements are removed as
well as null valued object fields. JSON that just consists of a single
null is not affected.

Author: Florents Tselai <florents.tselai@gmail.com>

Discussion: https://postgr.es/m/4BCECCD5-4F40-4313-9E98-9E16BEB0B01D@gmail.com
This commit is contained in:
Andrew Dunstan
2025-03-05 09:50:34 -05:00
parent 5ead85fbc8
commit 4603903d29
8 changed files with 190 additions and 9 deletions

View File

@ -17345,25 +17345,32 @@ ERROR: value too long for type character(2)
<indexterm> <indexterm>
<primary>json_strip_nulls</primary> <primary>json_strip_nulls</primary>
</indexterm> </indexterm>
<function>json_strip_nulls</function> ( <type>json</type> ) <function>json_strip_nulls</function> ( <parameter>target</parameter> <type>jsonb</type>, <optional>,<parameter>strip_in_arrays</parameter> <type>boolean</type> </optional> )
<returnvalue>json</returnvalue> <returnvalue>json</returnvalue>
</para> </para>
<para role="func_signature"> <para role="func_signature">
<indexterm> <indexterm>
<primary>jsonb_strip_nulls</primary> <primary>jsonb_strip_nulls</primary>
</indexterm> </indexterm>
<function>jsonb_strip_nulls</function> ( <type>jsonb</type> ) <function>jsonb_strip_nulls</function> ( <parameter>target</parameter> <type>jsonb</type>, <optional>,<parameter>strip_in_arrays</parameter> <type>boolean</type> </optional> )
<returnvalue>jsonb</returnvalue> <returnvalue>jsonb</returnvalue>
</para> </para>
<para> <para>
Deletes all object fields that have null values from the given JSON Deletes all object fields that have null values from the given JSON
value, recursively. Null values that are not object fields are value, recursively.
untouched. If <parameter>strip_in_arrays</parameter> is true (the default is false),
null array elements are also stripped.
Otherwise they are not stripped. Bare null values are never stripped.
</para> </para>
<para> <para>
<literal>json_strip_nulls('[{"f1":1, "f2":null}, 2, null, 3]')</literal> <literal>json_strip_nulls('[{"f1":1, "f2":null}, 2, null, 3]')</literal>
<returnvalue>[{"f1":1},2,null,3]</returnvalue> <returnvalue>[{"f1":1},2,null,3]</returnvalue>
</para></entry> </para>
<para>
<literal>jsonb_strip_nulls('[1,2,null,3,4]', true);</literal>
<returnvalue>[1,2,3,4]</returnvalue>
</para>
</entry>
</row> </row>
<row> <row>

View File

@ -607,6 +607,20 @@ LANGUAGE INTERNAL
STRICT STABLE PARALLEL SAFE STRICT STABLE PARALLEL SAFE
AS 'jsonb_path_query_first_tz'; AS 'jsonb_path_query_first_tz';
CREATE OR REPLACE FUNCTION
jsonb_strip_nulls(target jsonb, strip_in_arrays boolean DEFAULT false)
RETURNS jsonb
LANGUAGE INTERNAL
STRICT STABLE PARALLEL SAFE
AS 'jsonb_strip_nulls';
CREATE OR REPLACE FUNCTION
json_strip_nulls(target json, strip_in_arrays boolean DEFAULT false)
RETURNS json
LANGUAGE INTERNAL
STRICT STABLE PARALLEL SAFE
AS 'json_strip_nulls';
-- default normalization form is NFC, per SQL standard -- default normalization form is NFC, per SQL standard
CREATE OR REPLACE FUNCTION CREATE OR REPLACE FUNCTION
"normalize"(text, text DEFAULT 'NFC') "normalize"(text, text DEFAULT 'NFC')

View File

@ -286,6 +286,7 @@ typedef struct StripnullState
JsonLexContext *lex; JsonLexContext *lex;
StringInfo strval; StringInfo strval;
bool skip_next_null; bool skip_next_null;
bool strip_in_arrays;
} StripnullState; } StripnullState;
/* structure for generalized json/jsonb value passing */ /* structure for generalized json/jsonb value passing */
@ -4460,8 +4461,19 @@ sn_array_element_start(void *state, bool isnull)
{ {
StripnullState *_state = (StripnullState *) state; StripnullState *_state = (StripnullState *) state;
if (_state->strval->data[_state->strval->len - 1] != '[') /* If strip_in_arrays is enabled and this is a null, mark it for skipping */
if (isnull && _state->strip_in_arrays)
{
_state->skip_next_null = true;
return JSON_SUCCESS;
}
/* Only add a comma if this is not the first valid element */
if (_state->strval->len > 0 &&
_state->strval->data[_state->strval->len - 1] != '[')
{
appendStringInfoCharMacro(_state->strval, ','); appendStringInfoCharMacro(_state->strval, ',');
}
return JSON_SUCCESS; return JSON_SUCCESS;
} }
@ -4493,6 +4505,7 @@ Datum
json_strip_nulls(PG_FUNCTION_ARGS) json_strip_nulls(PG_FUNCTION_ARGS)
{ {
text *json = PG_GETARG_TEXT_PP(0); text *json = PG_GETARG_TEXT_PP(0);
bool strip_in_arrays = PG_NARGS() == 2 ? PG_GETARG_BOOL(1) : false;
StripnullState *state; StripnullState *state;
JsonLexContext lex; JsonLexContext lex;
JsonSemAction *sem; JsonSemAction *sem;
@ -4503,6 +4516,7 @@ json_strip_nulls(PG_FUNCTION_ARGS)
state->lex = makeJsonLexContext(&lex, json, true); state->lex = makeJsonLexContext(&lex, json, true);
state->strval = makeStringInfo(); state->strval = makeStringInfo();
state->skip_next_null = false; state->skip_next_null = false;
state->strip_in_arrays = strip_in_arrays;
sem->semstate = state; sem->semstate = state;
sem->object_start = sn_object_start; sem->object_start = sn_object_start;
@ -4520,12 +4534,13 @@ json_strip_nulls(PG_FUNCTION_ARGS)
} }
/* /*
* SQL function jsonb_strip_nulls(jsonb) -> jsonb * SQL function jsonb_strip_nulls(jsonb, bool) -> jsonb
*/ */
Datum Datum
jsonb_strip_nulls(PG_FUNCTION_ARGS) jsonb_strip_nulls(PG_FUNCTION_ARGS)
{ {
Jsonb *jb = PG_GETARG_JSONB_P(0); Jsonb *jb = PG_GETARG_JSONB_P(0);
bool strip_in_arrays = false;
JsonbIterator *it; JsonbIterator *it;
JsonbParseState *parseState = NULL; JsonbParseState *parseState = NULL;
JsonbValue *res = NULL; JsonbValue *res = NULL;
@ -4534,6 +4549,9 @@ jsonb_strip_nulls(PG_FUNCTION_ARGS)
JsonbIteratorToken type; JsonbIteratorToken type;
bool last_was_key = false; bool last_was_key = false;
if (PG_NARGS() == 2)
strip_in_arrays = PG_GETARG_BOOL(1);
if (JB_ROOT_IS_SCALAR(jb)) if (JB_ROOT_IS_SCALAR(jb))
PG_RETURN_POINTER(jb); PG_RETURN_POINTER(jb);
@ -4564,6 +4582,11 @@ jsonb_strip_nulls(PG_FUNCTION_ARGS)
(void) pushJsonbValue(&parseState, WJB_KEY, &k); (void) pushJsonbValue(&parseState, WJB_KEY, &k);
} }
/* if strip_in_arrays is set, also skip null array elements */
if (strip_in_arrays)
if (type == WJB_ELEM && v.type == jbvNull)
continue;
if (type == WJB_VALUE || type == WJB_ELEM) if (type == WJB_VALUE || type == WJB_ELEM)
res = pushJsonbValue(&parseState, type, &v); res = pushJsonbValue(&parseState, type, &v);
else else

View File

@ -9269,7 +9269,7 @@
proname => 'to_json', provolatile => 's', prorettype => 'json', proname => 'to_json', provolatile => 's', prorettype => 'json',
proargtypes => 'anyelement', prosrc => 'to_json' }, proargtypes => 'anyelement', prosrc => 'to_json' },
{ oid => '3261', descr => 'remove object fields with null values from json', { oid => '3261', descr => 'remove object fields with null values from json',
proname => 'json_strip_nulls', prorettype => 'json', proargtypes => 'json', proname => 'json_strip_nulls', prorettype => 'json', proargtypes => 'json bool',
prosrc => 'json_strip_nulls' }, prosrc => 'json_strip_nulls' },
{ oid => '3947', { oid => '3947',
@ -10205,7 +10205,7 @@
prorettype => 'jsonb', proargtypes => '', prorettype => 'jsonb', proargtypes => '',
prosrc => 'jsonb_build_object_noargs' }, prosrc => 'jsonb_build_object_noargs' },
{ oid => '3262', descr => 'remove object fields with null values from jsonb', { oid => '3262', descr => 'remove object fields with null values from jsonb',
proname => 'jsonb_strip_nulls', prorettype => 'jsonb', proargtypes => 'jsonb', proname => 'jsonb_strip_nulls', prorettype => 'jsonb', proargtypes => 'jsonb bool',
prosrc => 'jsonb_strip_nulls' }, prosrc => 'jsonb_strip_nulls' },
{ oid => '3478', { oid => '3478',

View File

@ -2504,6 +2504,56 @@ select json_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }');
{"a":{},"d":{}} {"a":{},"d":{}}
(1 row) (1 row)
-- json_strip_nulls (strip_in_arrays=true)
select json_strip_nulls(null, true);
json_strip_nulls
------------------
(1 row)
select json_strip_nulls('1', true);
json_strip_nulls
------------------
1
(1 row)
select json_strip_nulls('"a string"', true);
json_strip_nulls
------------------
"a string"
(1 row)
select json_strip_nulls('null', true);
json_strip_nulls
------------------
null
(1 row)
select json_strip_nulls('[1,2,null,3,4]', true);
json_strip_nulls
------------------
[1,2,3,4]
(1 row)
select json_strip_nulls('{"a":1,"b":null,"c":[2,null,3],"d":{"e":4,"f":null}}', true);
json_strip_nulls
-------------------------------
{"a":1,"c":[2,3],"d":{"e":4}}
(1 row)
select json_strip_nulls('[1,{"a":1,"b":null,"c":2},3]', true);
json_strip_nulls
---------------------
[1,{"a":1,"c":2},3]
(1 row)
-- an empty object is not null and should not be stripped
select json_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }', true);
json_strip_nulls
------------------
{"a":{},"d":{}}
(1 row)
-- json to tsvector -- json to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::json); select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::json);
to_tsvector to_tsvector

View File

@ -4153,6 +4153,56 @@ select jsonb_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }');
{"a": {}, "d": {}} {"a": {}, "d": {}}
(1 row) (1 row)
-- jsonb_strip_nulls (strip_in_arrays=true)
select jsonb_strip_nulls(null, true);
jsonb_strip_nulls
-------------------
(1 row)
select jsonb_strip_nulls('1', true);
jsonb_strip_nulls
-------------------
1
(1 row)
select jsonb_strip_nulls('"a string"', true);
jsonb_strip_nulls
-------------------
"a string"
(1 row)
select jsonb_strip_nulls('null', true);
jsonb_strip_nulls
-------------------
null
(1 row)
select jsonb_strip_nulls('[1,2,null,3,4]', true);
jsonb_strip_nulls
-------------------
[1, 2, 3, 4]
(1 row)
select jsonb_strip_nulls('{"a":1,"b":null,"c":[2,null,3],"d":{"e":4,"f":null}}', true);
jsonb_strip_nulls
--------------------------------------
{"a": 1, "c": [2, 3], "d": {"e": 4}}
(1 row)
select jsonb_strip_nulls('[1,{"a":1,"b":null,"c":2},3]', true);
jsonb_strip_nulls
--------------------------
[1, {"a": 1, "c": 2}, 3]
(1 row)
-- an empty object is not null and should not be stripped
select jsonb_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }', true);
jsonb_strip_nulls
--------------------
{"a": {}, "d": {}}
(1 row)
select jsonb_pretty('{"a": "test", "b": [1, 2, 3], "c": "test3", "d":{"dd": "test4", "dd2":{"ddd": "test5"}}}'); select jsonb_pretty('{"a": "test", "b": [1, 2, 3], "c": "test3", "d":{"dd": "test4", "dd2":{"ddd": "test5"}}}');
jsonb_pretty jsonb_pretty
---------------------------- ----------------------------

View File

@ -814,6 +814,25 @@ select json_strip_nulls('[1,{"a":1,"b":null,"c":2},3]');
-- an empty object is not null and should not be stripped -- an empty object is not null and should not be stripped
select json_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }'); select json_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }');
-- json_strip_nulls (strip_in_arrays=true)
select json_strip_nulls(null, true);
select json_strip_nulls('1', true);
select json_strip_nulls('"a string"', true);
select json_strip_nulls('null', true);
select json_strip_nulls('[1,2,null,3,4]', true);
select json_strip_nulls('{"a":1,"b":null,"c":[2,null,3],"d":{"e":4,"f":null}}', true);
select json_strip_nulls('[1,{"a":1,"b":null,"c":2},3]', true);
-- an empty object is not null and should not be stripped
select json_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }', true);
-- json to tsvector -- json to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::json); select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::json);

View File

@ -1102,6 +1102,24 @@ select jsonb_strip_nulls('[1,{"a":1,"b":null,"c":2},3]');
-- an empty object is not null and should not be stripped -- an empty object is not null and should not be stripped
select jsonb_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }'); select jsonb_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }');
-- jsonb_strip_nulls (strip_in_arrays=true)
select jsonb_strip_nulls(null, true);
select jsonb_strip_nulls('1', true);
select jsonb_strip_nulls('"a string"', true);
select jsonb_strip_nulls('null', true);
select jsonb_strip_nulls('[1,2,null,3,4]', true);
select jsonb_strip_nulls('{"a":1,"b":null,"c":[2,null,3],"d":{"e":4,"f":null}}', true);
select jsonb_strip_nulls('[1,{"a":1,"b":null,"c":2},3]', true);
-- an empty object is not null and should not be stripped
select jsonb_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }', true);
select jsonb_pretty('{"a": "test", "b": [1, 2, 3], "c": "test3", "d":{"dd": "test4", "dd2":{"ddd": "test5"}}}'); select jsonb_pretty('{"a": "test", "b": [1, 2, 3], "c": "test3", "d":{"dd": "test4", "dd2":{"ddd": "test5"}}}');
select jsonb_pretty('[{"f1":1,"f2":null},2,null,[[{"x":true},6,7],8],3]'); select jsonb_pretty('[{"f1":1,"f2":null},2,null,[[{"x":true},6,7],8],3]');