更新Postgres中的json字段

时间:2014-01-14 08:26:55

标签: python json postgresql

通过json字段查询Postgres 9.3非常棒。 但是我找不到更新json对象的正式方法,为此我使用基于上一篇文章(How do I modify fields inside the new PostgreSQL JSON datatype? plpythonu 编写的内部函数:

CREATE OR REPLACE FUNCTION json_update(data json, key text, value json)
RETURNS json AS
$BODY$
   from json import loads, dumps
   if key is None: return data
   js = loads(data)
   js[key] = value
   return dumps(js)
$BODY$
LANGUAGE plpythonu VOLATILE

当我的json更新仍然扁平而简单时,它的效果非常好。说“聊天”是在“GO_SESSION”表中提交的json类型,并包含 {“a”:“1”,“b”:“2”} ,以下代码将更改 'b'价值并将“聊天”变为 {“a”:“1”,“b”:“5”}

update "GO_SESSION" set chat=json_update(chat,'b','5') where id=3

问题在于我正在尝试将'b'另一个对象而不是一个简单的值:

update "GO_SESSION" set chat=json_update(chat,'b','{"name":"steve"}') where id=3

数据库中的结果是'b',其中包含转义字符串而不是真正的json对象:

{“a”:“1”,“b”:“{\”name \“:\”steve \“}”}

我尝试了不同的方式来转发或转储我的json以保持'b'为对象,但找不到解决方案。

谢谢

3 个答案:

答案 0 :(得分:11)

不需要eval。您的问题是您没有将值解码为json对象。

CREATE OR REPLACE FUNCTION json_update(data json, key text, value json)
RETURNS json AS
$BODY$
   from json import loads, dumps
   if key is None: return data
   js = loads(data)
   # you must decode 'value' with loads too:
   js[key] = loads(value)
   return dumps(js)
$BODY$
LANGUAGE plpythonu VOLATILE;

postgres=# SELECT json_update('{"a":1}', 'a', '{"innerkey":"innervalue"}');
            json_update            
-----------------------------------
 {"a": {"innerkey": "innervalue"}}
(1 row)

不仅如此,使用eval解码json是危险且不可靠的。这是不可靠的,因为json不是Python,它恰好在很多时候都有点像它。这是不安全的,因为你永远不知道你的评价是什么。在这种情况下,你很大程度上受PostgreSQL的json解析器保护:

postgres=# SELECT json_update(
postgres(#    '{"a":1}', 
postgres(#    'a', 
postgres(#    '__import__(''shutil'').rmtree(''/glad_this_is_not_just_root'')'
postgres(# );
ERROR:  invalid input syntax for type json
LINE 4:          '__import__(''shutil'').rmtree(''/glad_this_is_not_...
                 ^
DETAIL:  Token "__import__" is invalid.
CONTEXT:  JSON data, line 1: __import__...

...但如果有人能够将eval漏洞利用过去,那我就不会感到惊讶。所以这里的教训是:不要使用eval

答案 1 :(得分:0)

<强>解决

上述 plpythonu 函数的问题在于它将“value”与字符串相关,无论它是否实际上是一个复杂的json对象。解决它的关键是在值附近添加 eval()

js[key] = eval(value)

这样json字符串(在这个例子中名为'value')就会丢失它的外部双引号“{...}”并成为一个对象。

答案 2 :(得分:0)

适用于想要plv8(可信语言​​在Heroku等服务上使用)的人。我经常需要对json blob进行迁移或更新,并且直接在db上运行查询比下载所有数据,转换它然后发布更新要快得多。

CREATE EXTENSION plv8;
CREATE OR REPLACE FUNCTION json_replace_string(obj json, path text, value text, force boolean)
RETURNS json AS $$
if (value === null && !force) {
  return obj;
}
var nestedRe = /(\.|\[)/;
var scrub = /]/g;
path = path.replace(scrub, '');
var pathBits = path.split(nestedRe);
var len = pathBits.length;
var layer = obj;
for (var i = 0; i < len; i += 2) {
  if (layer === null || layer === undefined) return obj;
  var key = pathBits[i];
  if (key === '') continue;
  if (i === len - 1) {
    layer[key] = value;
  } else {
    if (force && typeof layer[key] === 'undefined') {
      layer[key] = pathBits[i+1] === '.' ? {} : [];
    }
    layer = layer[key];
  }
}
return obj;
$$ LANGUAGE plv8 IMMUTABLE;

您可以像这样使用

UPDATE my_table
SET blob=json_replace_string(blob, 'some.nested.path[5].to.object', 'new value', false)
WHERE some_condition;

force参数提供两个函数 - (1)允许您设置null值。如果您根据不存在的其他列动态生成值 - 例如blob->'non_existent_value'然后将null输入到函数中,您可能并不意味着将值设置为null。 (2)目的是强制创建嵌套路径,如果它在您正在变异的json对象中已经存在。例如

json_replace(string('{"some_key": "some_val"}', 'other_key', 'new_val', true)

给出

{"some_key": "some_val", "other_key": "new_val"}

你可以想象类似的功能来更新数字,删除键等。这基本上可以在新功能的早期阶段在postgres中实现类似mongo的功能,以便快速进行原型设计,并且随着我们的架构稳定,我们将事情分解为独立的列和表来获取最好的表现。