PostgreSQL compare two jsonb objects PostgreSQL compare two jsonb objects postgresql postgresql

PostgreSQL compare two jsonb objects


UPDATED

CREATE OR REPLACE FUNCTION jsonb_diff_val(val1 JSONB,val2 JSONB)RETURNS JSONB AS $$DECLARE  result JSONB;  v RECORD;BEGIN   result = val1;   FOR v IN SELECT * FROM jsonb_each(val2) LOOP     IF result @> jsonb_build_object(v.key,v.value)        THEN result = result - v.key;     ELSIF result ? v.key THEN CONTINUE;     ELSE        result = result || jsonb_build_object(v.key,'null');     END IF;   END LOOP;   RETURN result;END;$$ LANGUAGE plpgsql;

Query:

SELECT jsonb_diff_val(    '{"a":"aaa", "b":"bbb", "c":"ccc"}'::jsonb,    '{"a":"aaa", "b":"jjj", "d":"ddd"}'::jsonb);            jsonb_diff_val             --------------------------------------- {"b": "bbb", "c": "ccc", "d": "null"}(1 row)


I have created similar function that would scan the object recursively and will return the difference between new object and old object. I was not able to find a 'nicer' way to determine if jsonb object 'is empty' - so would be grateful for any suggestion how to simplify that. I plan to use it to keep track of updates made to the jsonb objects, so I store only what have changed.

Here is the function:

CREATE OR REPLACE FUNCTION jsonb_diff_val(val1 JSONB,val2 JSONB)RETURNS JSONB AS $$DECLARE    result JSONB;    object_result JSONB;    i int;    v RECORD;BEGIN    IF jsonb_typeof(val1) = 'null'    THEN         RETURN val2;    END IF;    result = val1;    FOR v IN SELECT * FROM jsonb_each(val1) LOOP        result = result || jsonb_build_object(v.key, null);    END LOOP;    FOR v IN SELECT * FROM jsonb_each(val2) LOOP        IF jsonb_typeof(val1->v.key) = 'object' AND jsonb_typeof(val2->v.key) = 'object'        THEN            object_result = jsonb_diff_val(val1->v.key, val2->v.key);            -- check if result is not empty             i := (SELECT count(*) FROM jsonb_each(object_result));            IF i = 0            THEN                 result = result - v.key; --if empty remove            ELSE                 result = result || jsonb_build_object(v.key,object_result);            END IF;        ELSIF val1->v.key = val2->v.key THEN             result = result - v.key;        ELSE            result = result || jsonb_build_object(v.key,v.value);        END IF;    END LOOP;    RETURN result;END;$$ LANGUAGE plpgsql;

Then simple query looks like this:

SELECT jsonb_diff_val(                                                                                                                                                                                                                                                               '{"a":"aaa", "b":{"b1":"b","b2":"bb","b3":{"b3a":"aaa","b3c":"ccc"}}, "c":"ccc"}'::jsonb,    '{"a":"aaa", "b":{"b1":"b1","b3":{"b3a":"aaa","b3c":"cccc"}}, "d":"ddd"}'::jsonb);                                jsonb_diff_val                                 ------------------------------------------------------------------------------- {"b": {"b1": "b1", "b2": null, "b3": {"b3c": "cccc"}}, "c": null, "d": "ddd"}(1 row)


Here is a solution without creating a new function;

SELECT    json_object_agg(COALESCE(old.key, new.key), old.value)  FROM json_each_text('{"a":"aaa", "b":"bbb", "c":"ccc"}') old  FULL OUTER JOIN json_each_text('{"a":"aaa", "b":"jjj", "d":"ddd"}') new ON new.key = old.key WHERE   new.value IS DISTINCT FROM old.value

The result is;

{"b" : "bbb", "c" : "ccc", "d" : null}

This method only compares first level of json. It does NOT traverse the whole object tree.