Transform or iterate over json(b) string values

Dmitry Dolgov, reviewed and lightly edited by me.
This commit is contained in:
Andrew Dunstan 2017-03-31 14:25:25 -04:00
parent 156d3882f8
commit c80b9920fc
2 changed files with 252 additions and 0 deletions

View File

@ -52,6 +52,25 @@ typedef struct OkeysState
int sent_count;
} OkeysState;
/* state for iterate_json_string_values function */
typedef struct IterateJsonStringValuesState
{
JsonLexContext *lex;
JsonIterateStringValuesAction action; /* an action that will be applied
to each json value */
void *action_state; /* any necessary context for iteration */
} IterateJsonStringValuesState;
/* state for transform_json_string_values function */
typedef struct TransformJsonStringValuesState
{
JsonLexContext *lex;
StringInfo strval; /* resulting json */
JsonTransformStringValuesAction action; /* an action that will be applied
to each json value */
void *action_state; /* any necessary context for transformation */
} TransformJsonStringValuesState;
/* state for json_get* functions */
typedef struct GetState
{
@ -271,6 +290,18 @@ static void setPathArray(JsonbIterator **it, Datum *path_elems,
int level, Jsonb *newval, uint32 nelems, int op_type);
static void addJsonbToParseState(JsonbParseState **jbps, Jsonb *jb);
/* function supporting iterate_json_string_values */
static void iterate_string_values_scalar(void *state, char *token, JsonTokenType tokentype);
/* functions supporting transform_json_string_values */
static void transform_string_values_object_start(void *state);
static void transform_string_values_object_end(void *state);
static void transform_string_values_array_start(void *state);
static void transform_string_values_array_end(void *state);
static void transform_string_values_object_field_start(void *state, char *fname, bool isnull);
static void transform_string_values_array_element_start(void *state, bool isnull);
static void transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype);
/*
* SQL function json_object_keys
@ -4130,3 +4161,208 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
}
}
}
/*
* Iterate over jsonb string values or elements, and pass them together with an
* iteration state to a specified JsonIterateStringValuesAction.
*/
void
iterate_jsonb_string_values(Jsonb *jb, void *state, JsonIterateStringValuesAction action)
{
JsonbIterator *it;
JsonbValue v;
JsonbIteratorToken type;
it = JsonbIteratorInit(&jb->root);
while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
{
if ((type == WJB_VALUE || type == WJB_ELEM) && v.type == jbvString)
{
action(state, v.val.string.val, v.val.string.len);
}
}
}
/*
* Iterate over json string values or elements, and pass them together with an
* iteration state to a specified JsonIterateStringValuesAction.
*/
void
iterate_json_string_values(text *json, void *action_state, JsonIterateStringValuesAction action)
{
JsonLexContext *lex = makeJsonLexContext(json, true);
JsonSemAction *sem = palloc0(sizeof(JsonSemAction));
IterateJsonStringValuesState *state = palloc0(sizeof(IterateJsonStringValuesState));
state->lex = lex;
state->action = action;
state->action_state = action_state;
sem->semstate = (void *) state;
sem->scalar = iterate_string_values_scalar;
pg_parse_json(lex, sem);
}
/*
* An auxiliary function for iterate_json_string_values to invoke a specified
* JsonIterateStringValuesAction.
*/
static void
iterate_string_values_scalar(void *state, char *token, JsonTokenType tokentype)
{
IterateJsonStringValuesState *_state = (IterateJsonStringValuesState *) state;
if (tokentype == JSON_TOKEN_STRING)
(*_state->action) (_state->action_state, token, strlen(token));
}
/*
* Iterate over a jsonb, and apply a specified JsonTransformStringValuesAction
* to every string value or element. Any necessary context for a
* JsonTransformStringValuesAction can be passed in the action_state variable.
* Function returns a copy of an original jsonb object with transformed values.
*/
Jsonb *
transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
JsonTransformStringValuesAction transform_action)
{
JsonbIterator *it;
JsonbValue v, *res = NULL;
JsonbIteratorToken type;
JsonbParseState *st = NULL;
text *out;
bool is_scalar = false;
it = JsonbIteratorInit(&jsonb->root);
is_scalar = it->isScalar;
while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
{
if ((type == WJB_VALUE || type == WJB_ELEM) && v.type == jbvString)
{
out = transform_action(action_state, v.val.string.val, v.val.string.len);
v.val.string.val = VARDATA_ANY(out);
v.val.string.len = VARSIZE_ANY_EXHDR(out);
res = pushJsonbValue(&st, type, type < WJB_BEGIN_ARRAY ? &v : NULL);
}
else
{
res = pushJsonbValue(&st, type, (type == WJB_KEY ||
type == WJB_VALUE ||
type == WJB_ELEM) ? &v : NULL);
}
}
if (res->type == jbvArray)
res->val.array.rawScalar = is_scalar;
return JsonbValueToJsonb(res);
}
/*
* Iterate over a json, and apply a specified JsonTransformStringValuesAction
* to every string value or element. Any necessary context for a
* JsonTransformStringValuesAction can be passed in the action_state variable.
* Function returns a StringInfo, which is a copy of an original json with
* transformed values.
*/
text *
transform_json_string_values(text *json, void *action_state,
JsonTransformStringValuesAction transform_action)
{
JsonLexContext *lex = makeJsonLexContext(json, true);
JsonSemAction *sem = palloc0(sizeof(JsonSemAction));
TransformJsonStringValuesState *state = palloc0(sizeof(TransformJsonStringValuesState));
state->lex = lex;
state->strval = makeStringInfo();
state->action = transform_action;
state->action_state = action_state;
sem->semstate = (void *) state;
sem->scalar = transform_string_values_scalar;
sem->object_start = transform_string_values_object_start;
sem->object_end = transform_string_values_object_end;
sem->array_start = transform_string_values_array_start;
sem->array_end = transform_string_values_array_end;
sem->scalar = transform_string_values_scalar;
sem->array_element_start = transform_string_values_array_element_start;
sem->object_field_start = transform_string_values_object_field_start;
pg_parse_json(lex, sem);
return cstring_to_text_with_len(state->strval->data, state->strval->len);
}
/*
* Set of auxiliary functions for transform_json_string_values to invoke a
* specified JsonTransformStringValuesAction for all values and left everything
* else untouched.
*/
static void
transform_string_values_object_start(void *state)
{
TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
appendStringInfoCharMacro(_state->strval, '{');
}
static void
transform_string_values_object_end(void *state)
{
TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
appendStringInfoCharMacro(_state->strval, '}');
}
static void
transform_string_values_array_start(void *state)
{
TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
appendStringInfoCharMacro(_state->strval, '[');
}
static void
transform_string_values_array_end(void *state)
{
TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
appendStringInfoCharMacro(_state->strval, ']');
}
static void
transform_string_values_object_field_start(void *state, char *fname, bool isnull)
{
TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
if (_state->strval->data[_state->strval->len - 1] != '{')
appendStringInfoCharMacro(_state->strval, ',');
/*
* Unfortunately we don't have the quoted and escaped string any more, so
* we have to re-escape it.
*/
escape_json(_state->strval, fname);
appendStringInfoCharMacro(_state->strval, ':');
}
static void
transform_string_values_array_element_start(void *state, bool isnull)
{
TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
if (_state->strval->data[_state->strval->len - 1] != '[')
appendStringInfoCharMacro(_state->strval, ',');
}
static void
transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype)
{
TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
if (tokentype == JSON_TOKEN_STRING)
{
text *out = (*_state->action) (_state->action_state, token, strlen(token));
escape_json(_state->strval, text_to_cstring(out));
}
else
appendStringInfoString(_state->strval, token);
}

View File

@ -14,6 +14,7 @@
#ifndef JSONAPI_H
#define JSONAPI_H
#include "jsonb.h"
#include "lib/stringinfo.h"
typedef enum
@ -131,4 +132,19 @@ extern JsonLexContext *makeJsonLexContextCstringLen(char *json,
*/
extern bool IsValidJsonNumber(const char *str, int len);
/* an action that will be applied to each value in iterate_json(b)_string_vaues functions */
typedef void (*JsonIterateStringValuesAction) (void *state, char *elem_value, int elem_len);
/* an action that will be applied to each value in transform_json(b)_string_values functions */
typedef text * (*JsonTransformStringValuesAction) (void *state, char *elem_value, int elem_len);
extern void iterate_jsonb_string_values(Jsonb *jb, void *state,
JsonIterateStringValuesAction action);
extern void iterate_json_string_values(text *json, void *action_state,
JsonIterateStringValuesAction action);
extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
JsonTransformStringValuesAction transform_action);
extern text *transform_json_string_values(text *json, void *action_state,
JsonTransformStringValuesAction transform_action);
#endif /* JSONAPI_H */