【问题标题】:Using a custom JSON encoder for SQLAlchemy's PostgreSQL JSONB implementation为 SQLAlchemy 的 PostgreSQL JSONB 实现使用自定义 JSON 编码器
【发布时间】:2016-07-26 02:01:13
【问题描述】:

我正在使用 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 方言时使用?

【问题讨论】:

    标签: python json postgresql sqlalchemy


    【解决方案1】:

    我在这里找到了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)
    

    【讨论】:

      【解决方案2】:

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

      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】:

        如果你和我一样,正在寻找一种使用 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)
        

        【讨论】:

        • 这很好用。澄清一下,Jökull 所指的 json.dumps 实际上是 flask.json.dumps,使用 import flask.json 然后 options.update(json_serializer=flask.json.dumps) 以避免混淆
        • 是的,发布没有导入语句的python,tsk,tsk,tsk。谢谢,@Nikolas B
        • Flask-SQLAlchemy 版本 3+ 现在支持传递engine_options。所以你可以做db = SQLAlchemy(engine_options={'json_serializer': flask.json.dumps})
        【解决方案4】:

        这是通过create_enginejson_serializer 关键字参数支持的,如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)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-10-09
          • 1970-01-01
          • 1970-01-01
          • 2021-10-19
          • 2017-01-26
          • 2020-05-14
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多