74

Using Postgres 9.4, I am looking for a way to merge two (or more) json or jsonb columns in a query. Consider the following table as an example:

  id | json1        | json2
----------------------------------------
  1   | {'a':'b'}   | {'c':'d'}
  2   | {'a1':'b2'} | {'f':{'g' : 'h'}}

Is it possible to have the query return the following:

  id | json
----------------------------------------
  1   | {'a':'b', 'c':'d'}
  2   | {'a1':'b2', 'f':{'g' : 'h'}}

Unfortunately, I can't define a function as described here. Is this possible with a "traditional" query?

Braiam
  • 1
  • 11
  • 47
  • 78
Robin
  • 3,512
  • 10
  • 39
  • 73

10 Answers10

147

In Postgres 9.5+ you can merge JSONB like this:

select json1 || json2;

Or, if it's JSON, coerce to JSONB if necessary:

select json1::jsonb || json2::jsonb;

Or:

select COALESCE(json1::jsonb||json2::jsonb, json1::jsonb, json2::jsonb);

(Otherwise, any null value in json1 or json2 returns an empty row)

For example:

select data || '{"foo":"bar"}'::jsonb from photos limit 1;
                               ?column?
----------------------------------------------------------------------
 {"foo": "bar", "preview_url": "https://unsplash.it/500/720/123"}

Kudos to @MattZukowski for pointing this out in a comment.

Adil B
  • 14,635
  • 11
  • 60
  • 78
Zubin
  • 9,422
  • 7
  • 48
  • 52
  • 30
    This doesn't actually deep merge.. `SELECT '{"a":[1,2,3]}'::jsonb || '{"a":[4,5,6]}'::jsonb;` Key/V pairs in the rhs clobber the LHS. – Evan Carroll Jan 17 '18 at 18:41
  • @EvanCarroll JSONB not allow duplicated key. So, it cannot do the deep merge. – jian Sep 28 '21 at 06:40
  • @JianHe Sure it can, and it'll clobber. Which is allowable in the spec. https://stackoverflow.com/a/23195243/124486 Try it `select '{"foo":42}'::jsonb || '{"foo":20}'::jsonb;` – Evan Carroll Sep 28 '21 at 13:36
  • @EvanCarroll. Now I am at 13.2, Windows version. select '{"foo":42}'::jsonb || '{"foo":20}'::jsonb; returns ` {"foo": 20} ` I thought you want `{"foo": [20,42]}` – jian Sep 29 '21 at 08:40
  • No, that's a recursive merge. It doesn't do that. ;) But you can do that manually for one level by merging the arrays, if you know the fields you want to merge beforehand. – Evan Carroll Sep 29 '21 at 15:15
  • `select json1 || json2;` returns null if any of them is null. Same with `coalesce(json1||json2)`. `coalesce(json1, json2)` returns only json1. – Braiam Oct 11 '21 at 17:38
  • If you have nulls, using an empty '{}'::jsonb` object would allow you to merge them correctly. – Braiam Oct 11 '21 at 17:45
54

Here is the complete list of build-in functions that can be used to create json objects in PostgreSQL. http://www.postgresql.org/docs/9.4/static/functions-json.html

  • row_to_json and json_object doest not allow you to define your own keys, so it can't be used here
  • json_build_object expect you to know by advance how many keys and values our object will have, that's the case in your example, but should not be the case in the real world
  • json_object looks like a good tool to tackle this problem but it forces us to cast our values to text so we can't use this one either

Well... ok, wo we can't use any classic functions.

Let's take a look at some aggregate functions and hope for the best... http://www.postgresql.org/docs/9.4/static/functions-aggregate.html

json_object_agg Is the only aggregate function that build objects, that's our only chance to tackle this problem. The trick here is to find the correct way to feed the json_object_agg function.

Here is my test table and data

CREATE TABLE test (
  id    SERIAL PRIMARY KEY,
  json1 JSONB,
  json2 JSONB
);

INSERT INTO test (json1, json2) VALUES
  ('{"a":"b", "c":"d"}', '{"e":"f"}'),
  ('{"a1":"b2"}', '{"f":{"g" : "h"}}');

And after some trials and errors with json_object here is a query you can use to merge json1 and json2 in PostgreSQL 9.4

WITH all_json_key_value AS (
  SELECT id, t1.key, t1.value FROM test, jsonb_each(json1) as t1
  UNION
  SELECT id, t1.key, t1.value FROM test, jsonb_each(json2) as t1
)
SELECT id, json_object_agg(key, value) 
FROM all_json_key_value 
GROUP BY id

For PostgreSQL 9.5+, look at Zubin's answer.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Clément Prévost
  • 8,000
  • 2
  • 36
  • 51
15

Looks like nobody proposed this kind of solution yet, so here's my take, using custom aggregate functions:

create or replace aggregate jsonb_merge_agg(jsonb)
(
    sfunc = jsonb_concat,
    stype = jsonb,
    initcond = '{}'
);

create or replace function jsonb_concat(a jsonb, b jsonb) returns jsonb
    as 'select $1 || $2'
    language sql
    immutable
    parallel safe
;

Note: this is using || which replaces existing values at same path instead of deeply merging them.

Now jsonb_merge_agg is accessible like so:

select jsonb_merge_agg(some_col) from some_table group by something;
Florian Klein
  • 8,692
  • 1
  • 32
  • 42
10

This function would merge nested json objects

create or replace function jsonb_merge(CurrentData jsonb,newData jsonb)
 returns jsonb
 language sql
 immutable
as $jsonb_merge_func$
 select case jsonb_typeof(CurrentData)
   when 'object' then case jsonb_typeof(newData)
     when 'object' then (
       select    jsonb_object_agg(k, case
                   when e2.v is null then e1.v
                   when e1.v is null then e2.v
                   when e1.v = e2.v then e1.v 
                   else jsonb_merge(e1.v, e2.v)
                 end)
       from      jsonb_each(CurrentData) e1(k, v)
       full join jsonb_each(newData) e2(k, v) using (k)
     )
     else newData
   end
   when 'array' then CurrentData || newData
   else newData
 end
$jsonb_merge_func$;
Sandeep Sinha
  • 101
  • 1
  • 3
  • 1
    Welcome to Stack Overflow! Thank you for this code snippet, which might provide some limited short-term help. A proper explanation [would greatly improve](//meta.stackexchange.com/q/114762) its long-term value by showing *why* this is a good solution to the problem, and would make it more useful to future readers with other, similar questions. Please [edit] your answer to add some explanation, including the assumptions you've made. – Toby Speight Apr 06 '18 at 08:49
  • 2
    Here my 2 cents: I've modifyed slightly, to allow only unique values in nested arrays, just replace `CurrentData || newData` in array part with `array_to_json(array(select distinct jsonb_array_elements( CurrentData || newData)))::jsonb`. **TODO:** not use JSON as intermdiate instead of JOSNB, find correct function for arrays – Bogdan Mart Apr 29 '19 at 11:24
9

Also you can tranform json into text, concatenate, replace and convert back to json. Using the same data from Clément you can do:

SELECT replace(
    (json1::text || json2::text), 
    '}{', 
    ', ')::json 
FROM test

You could also concatenate all json1 into single json with:

SELECT regexp_replace(
    array_agg((json1))::text,
    '}"(,)"{|\\| |^{"|"}$', 
    '\1', 
    'g'
)::json
FROM test

This is a very old solution, since 9.4 you should use json_object_agg and simple || concatenate operator. Keeping here just for reference.

caiohamamura
  • 2,260
  • 21
  • 23
6

However this question is answered already some time ago; the fact that when json1 and json2 contain the same key; the key appears twice in the document, does not seem to be best practice.

Therefore u can use this jsonb_merge function with PostgreSQL 9.5:

CREATE OR REPLACE FUNCTION jsonb_merge(jsonb1 JSONB, jsonb2 JSONB)
    RETURNS JSONB AS $$
    DECLARE
      result JSONB;
      v RECORD;
    BEGIN
       result = (
    SELECT json_object_agg(KEY,value)
    FROM
      (SELECT jsonb_object_keys(jsonb1) AS KEY,
              1::int AS jsb,
              jsonb1 -> jsonb_object_keys(jsonb1) AS value
       UNION SELECT jsonb_object_keys(jsonb2) AS KEY,
                    2::int AS jsb,
                    jsonb2 -> jsonb_object_keys(jsonb2) AS value ) AS t1
           );
       RETURN result;
    END;
    $$ LANGUAGE plpgsql;

The following query returns the concatenated jsonb columns, where the keys in json2 are dominant over the keys in json1:

select id, jsonb_merge(json1, json2) from test
Community
  • 1
  • 1
Joost Döbken
  • 3,450
  • 2
  • 35
  • 79
  • Doesn't work for PostgreSQL 9.4.4: SELECT jsonb_merge('{"11111":1, "2":2}'::jsonb, '{"11111":3}'::jsonb) – igilfanov May 20 '16 at 08:05
  • Tested on 9.5 - works well. To add priorities just replace UNION with UNION ALL in function. – Dmitry Jul 12 '16 at 09:05
  • @Dmitry it doesn't work at all. `select jsonb_merge('{"a":{"nested":1}}'::jsonb, '{"a":{"also nested":2}}'::jsonb);` should result in `a` with 2 properties, it doesn't. – Phill Dec 06 '16 at 09:07
  • @Phill; you are correct but u can solve this by adding a sublayer of jsonb_object_keys grouped by the keys with another aggregate function -- if the subvalue is a jsonb object. This can be repeated until you merge the subsubsublayer...etc – Joost Döbken Dec 06 '16 at 10:41
  • @API i created a new version with a JavaScript merge function: https://gist.github.com/phillip-haydon/54871b746201793990a18717af8d70dc I haven't tidied it up yet tho. Works on samples docs I have at work. – Phill Dec 06 '16 at 10:46
6

FYI, if someone's using jsonb in >= 9.5 and they only care about top-level elements being merged without duplicate keys, then it's as easy as using the || operator:

select '{"a1": "b2"}'::jsonb || '{"f":{"g" : "h"}}'::jsonb;
      ?column?           
-----------------------------
 {"a1": "b2", "f": {"g": "h"}}
(1 row)
Arthur Nascimento
  • 941
  • 1
  • 8
  • 3
4

Try this, if anyone having an issue for merging two JSON object

select table.attributes::jsonb || json_build_object('foo',1,'bar',2)::jsonb FROM table where table.x='y';
Piyush Sharma
  • 591
  • 7
  • 9
2
CREATE OR REPLACE FUNCTION jsonb_merge(pCurrentData jsonb, pMergeData jsonb, pExcludeKeys text[])
RETURNS jsonb IMMUTABLE LANGUAGE sql
AS $$
    SELECT json_object_agg(key,value)::jsonb
    FROM (
        WITH to_merge AS (
            SELECT * FROM jsonb_each(pMergeData) 
        )
        SELECT *
        FROM jsonb_each(pCurrentData)
        WHERE key NOT IN (SELECT key FROM to_merge)
     AND ( pExcludeKeys ISNULL OR key <> ALL(pExcludeKeys))
        UNION ALL
        SELECT * FROM to_merge
    ) t;
$$;

SELECT jsonb_merge('{"a": 1, "b": 9, "c": 3, "e":5}'::jsonb, '{"b": 2, "d": 4}'::jsonb, '{"c","e"}'::text[]) as jsonb

igilfanov
  • 353
  • 1
  • 11
2

works well as an alternative to || when recursive deep merge is required (found here) :

create or replace function jsonb_merge_recurse(orig jsonb, delta jsonb)
returns jsonb language sql as $$
    select
        jsonb_object_agg(
            coalesce(keyOrig, keyDelta),
            case
                when valOrig isnull then valDelta
                when valDelta isnull then valOrig
                when (jsonb_typeof(valOrig) <> 'object' or jsonb_typeof(valDelta) <> 'object') then valDelta
                else jsonb_merge_recurse(valOrig, valDelta)
            end
        )
    from jsonb_each(orig) e1(keyOrig, valOrig)
    full join jsonb_each(delta) e2(keyDelta, valDelta) on keyOrig = keyDelta
$$;
Kerem
  • 11,377
  • 5
  • 59
  • 58
user1767316
  • 3,276
  • 3
  • 37
  • 46