为SQLAlchemy的PostgreSQL JSONB实现使用自定义JSON编码器

时间:2016-04-05 22:15:50

标签: python json postgresql sqlalchemy

我正在使用SQLAlchemy的核心库访问一些PostgreSQL数据库。考虑我有下表:

create table foo (j jsonb);

以下python代码:

from decimal import *
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey, DateTime
from sqlalchemy.dialects.postgresql import JSONB
metadata = MetaData(schema="public")
foo = Table('foo', metadata,Column('f', JSONB))
d = Decimal(2)
ins = foo.insert().values(j = {'d': d})
# assuming engine is a valid sqlalchemy's connection
engine.execute(ins)

最后一句失败,出现以下错误:

StatementError("(builtins.TypeError) Decimal('2') is not JSON serializable",)

这就是我提出这个问题的原因:有没有办法为SQLAchemy指定一个自定义编码器,以便在将json数据编码成PostgreSQL方言时使用?

4 个答案:

答案 0 :(得分:20)

json_serializer下的create_engine关键字参数支持此功能,如sqlalchemy.dialects.postgresql.JSON所述:

def _default(val):
    if isinstance(val, Decimal):
        return str(val)
    raise TypeError()

def dumps(d):
    return json.dumps(d, default=_default)

engine = create_engine(..., json_serializer=dumps)

答案 1 :(得分:4)

如果你像我一样,正在寻找一种很好的方法来运行Flask-SQLAlchemy,这就是我所做的。如果导入并传递flask.json而不是标准库json模块,则会自动反序列化日期,日期时间和uuid.UUID个实例。

class HackSQLAlchemy(SQLAlchemy):
    """ Ugly way to get SQLAlchemy engine to pass the Flask JSON serializer
    to `create_engine`.

    See https://github.com/mitsuhiko/flask-sqlalchemy/pull/67/files

    """

    def apply_driver_hacks(self, app, info, options):
        options.update(json_serializer=json.dumps)
        super(HackSQLAlchemy, self).apply_driver_hacks(app, info, options)

答案 2 :(得分:2)

如果你正在使用Flask,你已经在flask.json中定义了一个扩展的JSONEncoder,它处理UUID,但不是Decimal。它可以使用json_serializer参数映射到SqlAlchemy引擎,如@ univerio的答案所示:

from flask import json

engine = create_engine(
    app.config['SQLALCHEMY_DATABASE_URI'],
    convert_unicode=True,
    json_serializer=json.dumps,
)

您可以使用以下内容进一步扩展Flask JSONEncoder以支持decimal.Decimal

import decimal

from flask import json

class CustomJSONEncoder(json.JSONEncoder):
    """
    Override Flask's JSONEncoder with the single method `default`, which 
    is called when the encoder doesn't know how to encode a specific type.
    """
    def default(self, obj):
        if type(obj) is decimal.Decimal:
            return str(obj)
        else:
            # raises TypeError: obj not JSON serializable
            return json.JSONEncoder.default(self, obj)

def init_json(app):
    """
    Use custom JSON encoder with Flask
    """
    app.json_encoder = CustomJSONEncoder

答案 3 :(得分:0)

我在这里找到了anwser:https://github.com/flask-restful/flask-restful/issues/116#issuecomment-128419699对其进行总结,以与Flask-SQLAlchemy一起运行:

from flask import Flask, json                                            
from decimal import Decimal              

# define encoder                                                    
class JSONEncoder(json.JSONEncoder):                    
    def default(self, value):                           
        if isinstance(value, Decimal):   
            return str(value)                           
        return json.JSONEncoder.default(self, value)    

class Config:
    RESTFUL_JSON = {}

    # make sure RESTful and Flask encoders stay synchronized
    @staticmethod
    def init_app(app):
        app.config['RESTFUL_JSON']['cls'] = app.json_encoder = JSONEncoder

app = Flask(__name__)
app.config.from_object(Config)
Config.init_app(app)