Merging Concatenating JSON(B) columns in query

Solution 1:

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.

Solution 2:

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.

Solution 3:

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$;

Solution 4:

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.