From 42664c0db2032b491b1e9926748d367c421540d0 Mon Sep 17 00:00:00 2001 From: Florents Tselai Date: Wed, 19 Feb 2025 23:12:58 +0200 Subject: [PATCH v2 3/3] Add implementation for json_strip_nulls(json,bool) --- src/backend/catalog/system_functions.sql | 7 ++++ src/backend/utils/adt/jsonfuncs.c | 16 +++++++- src/include/catalog/pg_proc.dat | 2 +- src/test/regress/expected/json.out | 50 ++++++++++++++++++++++++ src/test/regress/sql/json.sql | 19 +++++++++ 5 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index 54bf061036f..60306e09f0f 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -614,6 +614,13 @@ 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 CREATE OR REPLACE FUNCTION "normalize"(text, text DEFAULT 'NFC') diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index cc74fa55a11..9f43b58dba5 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -286,6 +286,7 @@ typedef struct StripnullState JsonLexContext *lex; StringInfo strval; bool skip_next_null; + bool strip_in_arrays; } StripnullState; /* structure for generalized json/jsonb value passing */ @@ -4460,8 +4461,19 @@ sn_array_element_start(void *state, bool isnull) { 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, ','); + } return JSON_SUCCESS; } @@ -4493,6 +4505,7 @@ Datum json_strip_nulls(PG_FUNCTION_ARGS) { text *json = PG_GETARG_TEXT_PP(0); + bool strip_in_arrays = PG_NARGS() == 2 ? PG_GETARG_BOOL(1) : false; StripnullState *state; JsonLexContext lex; JsonSemAction *sem; @@ -4503,6 +4516,7 @@ json_strip_nulls(PG_FUNCTION_ARGS) state->lex = makeJsonLexContext(&lex, json, true); state->strval = makeStringInfo(); state->skip_next_null = false; + state->strip_in_arrays = strip_in_arrays; sem->semstate = state; sem->object_start = sn_object_start; diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0a812d6a23b..d0ebb4b8541 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9238,7 +9238,7 @@ proname => 'to_json', provolatile => 's', prorettype => 'json', proargtypes => 'anyelement', prosrc => 'to_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' }, { oid => '3947', diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index 96c40911cb9..04b478cb468 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -2504,6 +2504,56 @@ select json_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }'); {"a":{},"d":{}} (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 select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::json); to_tsvector diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index 8251f4f4006..e9800b21ffe 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -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 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 select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::json); -- 2.48.1