Allow json{b}_strip_nulls to remove null array elements
authorAndrew Dunstan <[email protected]>
Wed, 5 Mar 2025 14:50:34 +0000 (09:50 -0500)
committerAndrew Dunstan <[email protected]>
Wed, 5 Mar 2025 15:04:02 +0000 (10:04 -0500)
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 <[email protected]>

Discussion: https://postgr.es/m/4BCECCD5-4F40-4313-9E98-9E16BEB0B01D@gmail.com

doc/src/sgml/func.sgml
src/backend/catalog/system_functions.sql
src/backend/utils/adt/jsonfuncs.c
src/include/catalog/pg_proc.dat
src/test/regress/expected/json.out
src/test/regress/expected/jsonb.out
src/test/regress/sql/json.sql
src/test/regress/sql/jsonb.sql

index bf31b1f3eeed7d6dfe883cf64d8f81bdface15d8..f97f0ce570a79668595826dfb2b57724a2b31cca 100644 (file)
@@ -17345,25 +17345,32 @@ ERROR:  value too long for type character(2)
         <indexterm>
          <primary>json_strip_nulls</primary>
         </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>
        </para>
        <para role="func_signature">
         <indexterm>
          <primary>jsonb_strip_nulls</primary>
         </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>
        </para>
        <para>
         Deletes all object fields that have null values from the given JSON
-        value, recursively.  Null values that are not object fields are
-        untouched.
+        value, recursively.
+        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>
         <literal>json_strip_nulls('[{"f1":1, "f2":null}, 2, null, 3]')</literal>
         <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>
index 86888cd3201f2032a18c2b8c7beb360c877624a2..566f308e4439de4ee78b27df439a1c2a3c63aab6 100644 (file)
@@ -607,6 +607,20 @@ LANGUAGE INTERNAL
 STRICT STABLE PARALLEL SAFE
 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
 CREATE OR REPLACE FUNCTION
   "normalize"(text, text DEFAULT 'NFC')
index c2e90f1a3bfe0c498a619276683e05552c17fd15..9f43b58dba5f51375e0ee6ac20705f3b2e8e8b2f 100644 (file)
@@ -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;
@@ -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
 jsonb_strip_nulls(PG_FUNCTION_ARGS)
 {
    Jsonb      *jb = PG_GETARG_JSONB_P(0);
+   bool        strip_in_arrays = false;
    JsonbIterator *it;
    JsonbParseState *parseState = NULL;
    JsonbValue *res = NULL;
@@ -4534,6 +4549,9 @@ jsonb_strip_nulls(PG_FUNCTION_ARGS)
    JsonbIteratorToken type;
    bool        last_was_key = false;
 
+   if (PG_NARGS() == 2)
+       strip_in_arrays = PG_GETARG_BOOL(1);
+
    if (JB_ROOT_IS_SCALAR(jb))
        PG_RETURN_POINTER(jb);
 
@@ -4564,6 +4582,11 @@ jsonb_strip_nulls(PG_FUNCTION_ARGS)
            (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)
            res = pushJsonbValue(&parseState, type, &v);
        else
index cd9422d0bacf5e8c4ebe98cc986681ae852da3f5..134b3dd868993ec0f251e5c0c7576cd7d88c20b9 100644 (file)
   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',
   prorettype => 'jsonb', proargtypes => '',
   prosrc => 'jsonb_build_object_noargs' },
 { 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' },
 
 { oid => '3478',
index 96c40911cb908ae789a2a6fd0a2b5f57e777599b..04b478cb468dbc1910eef938b7d971c225e0232d 100644 (file)
@@ -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                                
index 2baff931bf23b95a18d9d905ecf94dcbf3adeef4..5a1eb18aba29ec6e5eacfe7ace3ede46904b2b4b 100644 (file)
@@ -4153,6 +4153,56 @@ select jsonb_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }');
  {"a": {}, "d": {}}
 (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"}}}');
         jsonb_pretty        
 ----------------------------
index 8251f4f40060eb5d15d0514b5c9f9d29b6d1cb60..e9800b21ffed6d2a7af812d1ac3133171c806492 100644 (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
 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);
 
index 544bb610e2d65547efb89d04c9bfa9a450ffad05..57c11acddfee62503d9dd271b7b1f137c4a8380f 100644 (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
 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('[{"f1":1,"f2":null},2,null,[[{"x":true},6,7],8],3]');