@@ -105,6 +105,15 @@ static void populate_recordset_object_end(void *state);
105
105
static void populate_recordset_array_start (void * state );
106
106
static void populate_recordset_array_element_start (void * state , bool isnull );
107
107
108
+ /* semantic action functions for json_strip_nulls */
109
+ static void sn_object_start (void * state );
110
+ static void sn_object_end (void * state );
111
+ static void sn_array_start (void * state );
112
+ static void sn_array_end (void * state );
113
+ static void sn_object_field_start (void * state , char * fname , bool isnull );
114
+ static void sn_array_element_start (void * state , bool isnull );
115
+ static void sn_scalar (void * state , char * token , JsonTokenType tokentype );
116
+
108
117
/* worker function for populate_recordset and to_recordset */
109
118
static Datum populate_recordset_worker (FunctionCallInfo fcinfo , const char * funcname ,
110
119
bool have_record_arg );
@@ -225,6 +234,13 @@ typedef struct PopulateRecordsetState
225
234
MemoryContext fn_mcxt ; /* used to stash IO funcs */
226
235
} PopulateRecordsetState ;
227
236
237
+ /* state for json_strip_nulls */
238
+ typedef struct StripnullState {
239
+ JsonLexContext * lex ;
240
+ StringInfo strval ;
241
+ bool skip_next_null ;
242
+ } StripnullState ;
243
+
228
244
/* Turn a jsonb object into a record */
229
245
static void make_row_from_rec_and_jsonb (Jsonb * element ,
230
246
PopulateRecordsetState * state );
@@ -2996,3 +3012,184 @@ findJsonbValueFromContainerLen(JsonbContainer *container, uint32 flags,
2996
3012
2997
3013
return findJsonbValueFromContainer (container , flags , & k );
2998
3014
}
3015
+
3016
+ /*
3017
+ * Semantic actions for json_strip_nulls.
3018
+ *
3019
+ * Simply repeat the input on the output unless we encounter
3020
+ * a null object field. State for this is set when the field
3021
+ * is started and reset when the scalar action (which must be next)
3022
+ * is called.
3023
+ */
3024
+
3025
+ static void
3026
+ sn_object_start (void * state )
3027
+ {
3028
+ StripnullState * _state = (StripnullState * ) state ;
3029
+ appendStringInfoCharMacro (_state -> strval , '{' );
3030
+ }
3031
+
3032
+ static void
3033
+ sn_object_end (void * state )
3034
+ {
3035
+ StripnullState * _state = (StripnullState * ) state ;
3036
+ appendStringInfoCharMacro (_state -> strval , '}' );
3037
+ }
3038
+
3039
+ static void
3040
+ sn_array_start (void * state )
3041
+ {
3042
+ StripnullState * _state = (StripnullState * ) state ;
3043
+ appendStringInfoCharMacro (_state -> strval , '[' );
3044
+ }
3045
+
3046
+ static void
3047
+ sn_array_end (void * state )
3048
+ {
3049
+ StripnullState * _state = (StripnullState * ) state ;
3050
+ appendStringInfoCharMacro (_state -> strval , ']' );
3051
+ }
3052
+
3053
+ static void
3054
+ sn_object_field_start (void * state , char * fname , bool isnull )
3055
+ {
3056
+ StripnullState * _state = (StripnullState * ) state ;
3057
+
3058
+ if (isnull )
3059
+ {
3060
+ /*
3061
+ * The next thing must be a scalar or isnull couldn't be true,
3062
+ * so there is no danger of this state being carried down
3063
+ * into a nested object or array. The flag will be reset in the
3064
+ * scalar action.
3065
+ */
3066
+ _state -> skip_next_null = true;
3067
+ return ;
3068
+ }
3069
+
3070
+ if (_state -> strval -> data [_state -> strval -> len - 1 ] != '{' )
3071
+ appendStringInfoCharMacro (_state -> strval , ',' );
3072
+
3073
+ /*
3074
+ * Unfortunately we don't have the quoted and escaped string any more,
3075
+ * so we have to re-escape it.
3076
+ */
3077
+ escape_json (_state -> strval ,fname );
3078
+
3079
+ appendStringInfoCharMacro (_state -> strval , ':' );
3080
+ }
3081
+
3082
+ static void
3083
+ sn_array_element_start (void * state , bool isnull )
3084
+ {
3085
+ StripnullState * _state = (StripnullState * ) state ;
3086
+
3087
+ if (_state -> strval -> data [_state -> strval -> len - 1 ] != '[' )
3088
+ appendStringInfoCharMacro (_state -> strval , ',' );
3089
+ }
3090
+
3091
+ static void
3092
+ sn_scalar (void * state , char * token , JsonTokenType tokentype )
3093
+ {
3094
+ StripnullState * _state = (StripnullState * ) state ;
3095
+
3096
+ if (_state -> skip_next_null )
3097
+ {
3098
+ Assert (tokentype == JSON_TOKEN_NULL );
3099
+ _state -> skip_next_null = false;
3100
+ return ;
3101
+ }
3102
+
3103
+ if (tokentype == JSON_TOKEN_STRING )
3104
+ escape_json (_state -> strval , token );
3105
+ else
3106
+ appendStringInfoString (_state -> strval , token );
3107
+ }
3108
+
3109
+ /*
3110
+ * SQL function json_strip_nulls(json) -> json
3111
+ */
3112
+ Datum
3113
+ json_strip_nulls (PG_FUNCTION_ARGS )
3114
+ {
3115
+ text * json = PG_GETARG_TEXT_P (0 );
3116
+ StripnullState * state ;
3117
+ JsonLexContext * lex ;
3118
+ JsonSemAction * sem ;
3119
+
3120
+ lex = makeJsonLexContext (json , true);
3121
+ state = palloc0 (sizeof (StripnullState ));
3122
+ sem = palloc0 (sizeof (JsonSemAction ));
3123
+
3124
+ state -> strval = makeStringInfo ();
3125
+ state -> skip_next_null = false;
3126
+ state -> lex = lex ;
3127
+
3128
+ sem -> semstate = (void * ) state ;
3129
+ sem -> object_start = sn_object_start ;
3130
+ sem -> object_end = sn_object_end ;
3131
+ sem -> array_start = sn_array_start ;
3132
+ sem -> array_end = sn_array_end ;
3133
+ sem -> scalar = sn_scalar ;
3134
+ sem -> array_element_start = sn_array_element_start ;
3135
+ sem -> object_field_start = sn_object_field_start ;
3136
+
3137
+ pg_parse_json (lex , sem );
3138
+
3139
+ PG_RETURN_TEXT_P (cstring_to_text_with_len (state -> strval -> data ,
3140
+ state -> strval -> len ));
3141
+
3142
+ }
3143
+
3144
+ /*
3145
+ * SQL function jsonb_strip_nulls(jsonb) -> jsonb
3146
+ */
3147
+ Datum
3148
+ jsonb_strip_nulls (PG_FUNCTION_ARGS )
3149
+ {
3150
+ Jsonb * jb = PG_GETARG_JSONB (0 );
3151
+ JsonbIterator * it ;
3152
+ JsonbParseState * parseState = NULL ;
3153
+ JsonbValue * res = NULL ;
3154
+ int type ;
3155
+ JsonbValue v ,k ;
3156
+ bool last_was_key = false;
3157
+
3158
+ if (JB_ROOT_IS_SCALAR (jb ))
3159
+ PG_RETURN_POINTER (jb );
3160
+
3161
+ it = JsonbIteratorInit (& jb -> root );
3162
+
3163
+ while ((type = JsonbIteratorNext (& it , & v , false)) != WJB_DONE )
3164
+ {
3165
+ Assert ( ! (type == WJB_KEY && last_was_key ));
3166
+
3167
+ if (type == WJB_KEY )
3168
+ {
3169
+ /* stash the key until we know if it has a null value */
3170
+ k = v ;
3171
+ last_was_key = true;
3172
+ continue ;
3173
+ }
3174
+
3175
+ if (last_was_key )
3176
+ {
3177
+ /* if the last element was a key this one can't be */
3178
+ last_was_key = false;
3179
+
3180
+ /* skip this field if value is null */
3181
+ if (type == WJB_VALUE && v .type == jbvNull )
3182
+ continue ;
3183
+
3184
+ /* otherwise, do a delayed push of the key */
3185
+ res = pushJsonbValue (& parseState , WJB_KEY , & k );
3186
+ }
3187
+
3188
+ if (type == WJB_VALUE || type == WJB_ELEM )
3189
+ res = pushJsonbValue (& parseState , type , & v );
3190
+ else
3191
+ res = pushJsonbValue (& parseState , type , NULL );
3192
+ }
3193
+
3194
+ PG_RETURN_POINTER (JsonbValueToJsonb (res ));
3195
+ }
0 commit comments