Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am using SQLAlchemy's core library to access some PostgreSQL database. Consider I have the following table:

create table foo (j jsonb);

And the following python code:

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?

like image 566
Pablo Santa Cruz Avatar asked Apr 05 '16 22:04

Pablo Santa Cruz


People also ask

Should I use JSON or Jsonb Postgres?

In general, most applications should prefer to store JSON data as jsonb , unless there are quite specialized needs, such as legacy assumptions about ordering of object keys. RFC 7159 specifies that JSON strings should be encoded in UTF8.

How do I query Jsonb data in PostgreSQL?

Querying the JSON documentPostgreSQL has two native operators -> and ->> to query JSON documents. The first operator -> returns a JSON object, while the operator ->> returns text. These operators work on both JSON as well as JSONB columns. There are additional operators available for JSONB columns.

Does SQLAlchemy work with PostgreSQL?

PostgreSQL supports sequences, and SQLAlchemy uses these as the default means of creating new primary key values for integer-based primary key columns.

When should I use JSON in Postgres?

So, the question is: should you use JSON? At the end of the day, Postgres' JSON type simply provides JSON validation on a text field. If you're storing some form of log data you rarely need to query, JSON can work fine. Because it's so simple, it will have a lot higher write throughput.


2 Answers

This is supported via the json_serializer keyword argument to create_engine, as documented under 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)
like image 88
univerio Avatar answered Oct 05 '22 07:10

univerio


If you, like me, are finding a nice way to get this running with Flask-SQLAlchemy, this is what I did. 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.

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)
like image 40
Jökull Avatar answered Oct 05 '22 06:10

Jökull