简体   繁体   English

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

[英]Using a custom JSON encoder for SQLAlchemy's PostgreSQL JSONB implementation

I am using SQLAlchemy's core library to access some PostgreSQL database. 我正在使用SQLAlchemy的核心库来访问一些PostgreSQL数据库。 Consider I have the following table: 考虑我有下表:

create table foo (j jsonb);

And the following python code: 以下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)

This last sentence fails with the following error: 最后一句失败,出现以下错误:

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

Which is why I am asking this question: Is there a way to specify a custom encoder for SQLAchemy to use when encoding json data into PostgreSQL dialect? 这就是我提出这个问题的原因:有没有办法为SQLAchemy指定一个自定义编码器,以便在将json数据编码成PostgreSQL方言时使用?

This is supported via the json_serializer keyword argument to create_engine , as documented under sqlalchemy.dialects.postgresql.JSON : 这是通过支持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)

If you, like me, are finding a nice way to get this running with Flask-SQLAlchemy, this is what I did. 如果你像我一样,找到了一个很好的方法来运行Flask-SQLAlchemy,这就是我所做的。 If you import and pass flask.json instead of the standard library json module, you'll get automatic deserialization of dates, datetimes and uuid.UUID instances. 如果导入并传递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)

If you're using Flask, you already have an extended JSONEncoder defined in flask.json which handles UUID , but not Decimal . 如果您使用的瓶,你已经在限定的加长JSONEncoder flask.json它处理UUID ,而不是Decimal It can be mapped into the SqlAlchemy engine with the json_serializer param as in @univerio's answer: 它可以使用json_serializer参数映射到SqlAlchemy引擎,如@ univerio的答案:

from flask import json

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

You can further extend the Flask JSONEncoder to support decimal.Decimal with the following: 您可以使用以下内容进一步扩展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

I found anwser here: https://github.com/flask-restful/flask-restful/issues/116#issuecomment-128419699 Summing it up, to run it with Flask-SQLAlchemy: 我在这里找到了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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM