问题描述
我的端点,接受客户端请求HTTP方法PATCH,这是JSON Merge Patch(RFC 7396)的有效内容类型。 https://tools.ietf.org/html/rfc7396
我们使用了Oracle,使用函数 json_merge_patch ()
可以很方便地更新数据库中的json内容。UPDATE table_name SET po_document =
json_mergepatch(po_document,json_by_rfc7396);
我没有在Postgres,jsonb_set()
和运算符||
和#-
中找到类似的功能,这对于深层json内容不方便。
示例:
SELECT json_merge_patch(
'{"root": {"k1": "v1","k2": "v2"} }'::jsonb,-- source JSON
'{"root": {"k1": "upd","k2": null,"k3": "new"} }'::jsonb -- JSON patch (RFC 7396)
)
{"root": {"k1": "upd","k3": "new"} }
解决方法
该规范非常简单,可以进行递归操作。
create or replace function jsonb_merge_patch(v_basedoc jsonb,v_patch jsonb)
returns jsonb as $$
with recursive patchexpand as(
select '{}'::text[] as jpath,v_patch as jobj,jsonb_typeof(v_patch) as jtype,0 as lvl
union all
select p.jpath||o.key as jpath,p.jobj->o.key as jobj,jsonb_typeof(p.jobj->o.key) as jtype,p.lvl + 1 as lvl
from patchexpand p
cross join lateral jsonb_each(case when p.jtype = 'object' then p.jobj else '{}'::jsonb end) as o(key,value)
),pathnum as (
select *,row_number() over (order by lvl,jpath) as rn
from patchexpand
),apply as (
select case
when jsonb_typeof(v_basedoc) = 'object' then v_basedoc
else '{}'::jsonb
end as basedoc,p.rn
from pathnum p
where p.rn = 1
union all
select case
when p.jtype = 'object' then a.basedoc
when p.jtype = 'null' then a.basedoc #- p.jpath
else jsonb_set(a.basedoc,p.jpath,p.jobj)
end as basedoc,p.rn
from apply a
join pathnum p
on p.rn = a.rn + 1
)
select case
when jsonb_typeof(v_patch) != 'object' then v_patch
else basedoc
end
from apply
order by rn desc
limit 1;
$$
language sql;
使用RFC中的示例进行测试:
select jsonb_pretty(jsonb_merge_patch('{
"title": "Goodbye!","author" : {
"givenName" : "John","familyName" : "Doe"
},"tags":[ "example","sample" ],"content": "This will be unchanged"
}'::jsonb,'{
"title": "Hello!","phoneNumber": "+01-123-456-7890","author": {
"familyName": null
},"tags": [ "example" ]
}'::jsonb));
jsonb_pretty
------------------------------------------
{ +
"tags": [ +
"example" +
],+
"title": "Hello!",+
"author": { +
"givenName": "John" +
},+
"content": "This will be unchanged",+
"phoneNumber": "+01-123-456-7890" +
}
(1 row)
使用问题中的示例进行测试:
SELECT jsonb_merge_patch(
'{"root": {"k1": "v1","k2": "v2"} }'::jsonb,-- source JSON
'{"root": {"k1": "upd","k2": null,"k3": "new"} }'::jsonb -- JSON patch (RFC 7396)
);
jsonb_merge_patch
--------------------------------------
{"root": {"k1": "upd","k3": "new"}}
(1 row)
,
根据this post,在这里留下2美分以寻求更紧凑的解决方案:
CREATE OR REPLACE FUNCTION json_merge_patch("target" jsonb,"patch" jsonb) RETURNS jsonb AS $$
BEGIN
RETURN COALESCE(jsonb_object_agg(
COALESCE("tkey","pkey"),CASE
WHEN "tval" ISNULL THEN "pval"
WHEN "pval" ISNULL THEN "tval"
WHEN jsonb_typeof("tval") != 'object' OR jsonb_typeof("pval") != 'object' THEN "pval"
ELSE json_merge_patch("tval","pval")
END
),'{}'::jsonb)
FROM jsonb_each("target") e1("tkey","tval")
FULL JOIN jsonb_each("patch") e2("pkey","pval")
ON "tkey" = "pkey"
WHERE jsonb_typeof("pval") != 'null'
OR "pval" ISNULL;
END;
$$ LANGUAGE plpgsql;
就我而言,它遵循RFC 7396。