Skip to content

Commit

Permalink
Added json_set_value(obj, path, value) which the item at path in obj …
Browse files Browse the repository at this point in the history
…to the supplied value
  • Loading branch information
claesjac committed Sep 26, 2011
1 parent 6862965 commit cee7b78
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 2 deletions.
1 change: 1 addition & 0 deletions Changes
@@ -1,6 +1,7 @@
Revision history for Postgres extension pg-json

0.0.2 ...
- added json_set_value(json, text, json) which sets the value of a json value at a given path
- added json_concat(json, json) which concatenates two structures and it's operator equivalent ||

0.0.1 Mon Sep 05
Expand Down
5 changes: 5 additions & 0 deletions pg-json--0.0.2.sql
Expand Up @@ -24,6 +24,11 @@ CREATE OR REPLACE FUNCTION json_get_value(data json, path text)
AS '$libdir/pg-json'
LANGUAGE C IMMUTABLE STRICT;

CREATE OR REPLACE FUNCTION json_set_value(data json, path text, value json)
RETURNS json
AS '$libdir/pg-json'
LANGUAGE C IMMUTABLE STRICT;

CREATE OR REPLACE FUNCTION json_equals(this json, that json)
RETURNS boolean
AS '$libdir/pg-json'
Expand Down
128 changes: 126 additions & 2 deletions pg-json.c
Expand Up @@ -63,8 +63,6 @@ Datum json_out(PG_FUNCTION_ARGS) {
PG_RETURN_CSTRING(result);
}

PG_FUNCTION_INFO_V1(json_get_value);

static json_t *get_value_at_path(json_t *obj, const char *path) {
long ix = 0, offset = 0;
json_t *rv = NULL;
Expand Down Expand Up @@ -175,6 +173,8 @@ static text *json_t_to_text(json_t *rv) {
return NULL;
}

PG_FUNCTION_INFO_V1(json_get_value);

Datum json_get_value(PG_FUNCTION_ARGS)
{
Json* json = (Json *) PG_GETARG_POINTER(0);
Expand Down Expand Up @@ -205,6 +205,130 @@ Datum json_get_value(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(t);
}

PG_FUNCTION_INFO_V1(json_set_value);

static void set_value_at_path(json_t *obj, const char *path, json_t *new_v) {
long ix = 0, offset = 0;
char *key;
const char *orig_path = path;

if (*path == '[') {
if (!json_is_array(obj)) {
elog(ERROR, "Object is not an array for path '%s'", path);
return;
}

offset = 1;
while (isdigit(path[offset])) {
offset++;
}
if (offset == 1) {
elog(ERROR, "Missing array index");
return;
}
if (path[offset] != ']') {
elog(ERROR, "Invalid path, expected ']' but got '%c'", path[offset]);
return;
}

ix = strtol(path + 1, NULL, 10);

if (ix >= json_array_size(obj)) {
elog(ERROR, "Subscript out of range: %d > %d", (int) ix, (int) json_array_size(obj) - 1);
return;
}

if (path[offset + 1] == '\0') {
json_array_set(obj, ix, new_v);
return;
}
else {
obj = json_array_get(obj, ix);
if (obj == NULL) {
return;
}
set_value_at_path(obj, path + offset + 1, new_v);
}
}
else {
/* Skip separators */
if (*path == '.') { path++; }

while(path[offset] != '\0' && path[offset] != '[' && path[offset] != '.') {
offset++;
}

if (offset == 0) {
elog(ERROR, "No key at %s:%d", __FILE__, __LINE__);
return;
}

key = calloc(offset + 1, sizeof(char));
strncpy(key, path, offset);

if (path[offset] == '\0') {
if (json_object_set_new(obj, key, new_v) != 0) {
elog(ERROR, "Failed to set key '%s'", key);
free(key);
return;
}
}
else {
obj = json_object_get(obj, key);
free(key);

if (obj == NULL) {
return;
}

set_value_at_path(obj, path + offset, new_v);
}
}
}

Datum json_set_value(PG_FUNCTION_ARGS)
{
Json* json;
size_t len;
json_t* root;
json_t* sv;
char* str;

json_error_t error;

json = (Json *) PG_GETARG_POINTER(0);
len = VARSIZE_ANY_EXHDR(json);
root = json_loadb(VARDATA(json), len, 0, &error);
if (!root) {
elog(ERROR, "Failed to parse json: %s", error.text);
}

json = (Json *) PG_GETARG_POINTER(2);
len = VARSIZE_ANY_EXHDR(json);
sv = json_loadb(VARDATA(json), len, 0, &error);
if (!sv) {
elog(ERROR, "Failed to parse value: %s", error.text);
}

set_value_at_path(root, (const char *) text_to_cstring(PG_GETARG_TEXT_P(1)), sv);

str = json_dumps(root, JSON_COMPACT);

/* Avoid leaks */
json_decref(root);
json_decref(sv);

/* Convert back to a json type */
len = strlen(str);
json = (Json *) palloc(len + VARHDRSZ);
SET_VARSIZE(json, len + VARHDRSZ);
memcpy(VARDATA(json), str, len);

free(str);

PG_RETURN_POINTER(json);
}

/* Comparision */
static int json_compare(Json *ja, Json *jb) {
size_t len;
Expand Down
5 changes: 5 additions & 0 deletions test.sql
Expand Up @@ -5,6 +5,7 @@ SELECT json_get_value('{"foo": "bar", "quax": "zorg"}','quax') = 'zorg';
SELECT json_get_value('{"foo": { "quax": "zorg" } }','foo.quax') = 'zorg';
SELECT json_get_value('{"foo": [{ "quax": "zorg" }] }','foo[0].quax') = 'zorg';


-- ~ is the operator version of json_get_value
SELECT '{"foo":"bar"}'::json ~ 'foo' = 'bar';

Expand All @@ -29,3 +30,7 @@ SELECT json_concat('[1, 2, 3]','[4, 5, 6]') = '[1, 2, 3, 4, 5, 6]';
SELECT '{"a":"b"}'::json || '{"c":"d"}'::json = '{"a":"b","c":"d"}'::json;
SELECT '[1, 2, 3]'::json || '[4, 5, 6]'::json = '[1, 2, 3, 4, 5, 6]'::json;

-- Setting by path
SELECT json_equals(json_set_value('{"foo":"bar"}', 'foo', '{"baz":"quax"}'), '{"foo":{"baz":"quax"}}');
SELECT json_equals(json_set_value('[1,2,3]', '[1]', '[1,2]'), '[1,[1,2],3]');
SELECT json_equals(json_set_value('{"foo":[1,{"quax":1}]}', 'foo[1].quax', '[1,2]'), '{"foo":[1,{"quax":[1,2]}]}');

0 comments on commit cee7b78

Please sign in to comment.