Most notably A @> B AND B @> A
will signify TRUE
if they are both equal JSONB objects.
However, be careful when assuming that it works for all kinds of JSONB values, as demonstrated with the following query:
select
old,
new,
NOT(old @> new AND new @> old) as changed
from (
values
(
'{"a":"1", "b":"2", "c": {"d": 3}}'::jsonb,
'{"b":"2", "a":"1", "c": {"d": 3, "e": 4}}'::jsonb
),
(
'{"a":"1", "b":"2", "c": {"d": 3, "e": 4}}'::jsonb,
'{"b":"2", "a":"1", "c": {"d": 3}}'::jsonb
),
(
'[1, 2, 3]'::jsonb,
'[3, 2, 1]'::jsonb
),
(
'{"a": 1, "b": 2}'::jsonb,
'{"b":2, "a":1}'::jsonb
),
(
'{"a":[1, 2, 3]}'::jsonb,
'{"b":[3, 2, 1]}'::jsonb
)
) as t (old, new)
Problems with this approach are that JSONB arrays are not compared correctly, as in JSON [1, 2, 3] != [3, 2, 1]
but Postgres returns TRUE
nevertheless.
A correct solution will recursively iterate through the contents of the json and comparing arrays and objects differently. I have quickly built a set of functions that accomplishes just that.
Use them like SELECT jsonb_eql('[1, 2, 3]'::jsonb, '[3, 2, 1]'::jsonb)
(the result is FALSE
).
CREATE OR REPLACE FUNCTION jsonb_eql (a JSONB, b JSONB) RETURNS BOOLEAN AS $$
DECLARE
BEGIN
IF (jsonb_typeof(a) != jsonb_typeof(b)) THEN
RETURN FALSE;
ELSE
IF (jsonb_typeof(a) = 'object') THEN
RETURN jsonb_object_eql(a, b);
ELSIF (jsonb_typeof(a) = 'array') THEN
RETURN jsonb_array_eql(a, b);
ELSIF (COALESCE(jsonb_typeof(a), 'null') = 'null') THEN
RETURN COALESCE(a, 'null'::jsonb) = 'null'::jsonb AND COALESCE(b, 'null'::jsonb) = 'null'::jsonb;
ELSE
RETURN coalesce(a = b, FALSE);
END IF;
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION jsonb_object_eql (a JSONB, b JSONB) RETURNS BOOLEAN AS $$
DECLARE
_key_a text;
_val_a jsonb;
_key_b text;
_val_b jsonb;
BEGIN
IF (jsonb_typeof(a) != jsonb_typeof(b)) THEN
RETURN FALSE;
ELSIF (jsonb_typeof(a) != 'object') THEN
RETURN jsonb_eql(a, b);
ELSE
FOR _key_a, _val_a, _key_b, _val_b IN
SELECT t1.key, t1.value, t2.key, t2.value FROM jsonb_each(a) t1
LEFT OUTER JOIN (
SELECT * FROM jsonb_each(b)
) t2 ON (t1.key = t2.key)
LOOP
IF (_key_a != _key_b) THEN
RETURN FALSE;
ELSE
RETURN jsonb_eql(_val_a, _val_b);
END IF;
END LOOP;
RETURN a = b;
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION jsonb_array_eql (a JSONB, b JSONB) RETURNS BOOLEAN AS $$
DECLARE
_val_a jsonb;
_val_b jsonb;
BEGIN
IF (jsonb_typeof(a) != jsonb_typeof(b)) THEN
RETURN FALSE;
ELSIF (jsonb_typeof(a) != 'array') THEN
RETURN jsonb_eql(a, b);
ELSE
FOR _val_a, _val_b IN
SELECT jsonb_array_elements(a), jsonb_array_elements(b)
LOOP
IF (NOT(jsonb_eql(_val_a, _val_b))) THEN
RETURN FALSE;
END IF;
END LOOP;
RETURN TRUE;
END IF;
END;
$$ LANGUAGE plpgsql;