JSON是一种使用相当普遍的数据协议类型,对于保存数据十分方便,但是老版本的MySQL又不支持JSON格式,所以只能使用TEXT来保存JSON类型的字符串。
对于字符串类型的JSON,在使用的时候必须进行一步转化,把字符串转化为Python中的DICT,为了能够方便的使用SQLALCHEMY,遂写了如下的方法:
import json
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import Insert
# !! 实例化后的用法等同于内置的 dict
class JsonValue(object):
def __init__(self, attr_name):
self.attr_name = attr_name
self.name = '_' + attr_name
def __get__(self, obj, type=None):
if obj is None:
return self
value = obj.__dict__.get(self.name, None)
if value is None:
_v = getattr(obj, self.attr_name)
if not _v:
value = {}
obj.__dict__[self.name] = value
return value
value = json.loads(_v)
obj.__dict__[self.name] = value
return value
def __set__(self, instance, value):
if value is None:
return
if not isinstance(value, dict):
raise ValueError('value should be a dict')
setattr(instance, self.attr_name, json.dumps(value, indent=2, sort_keys=True))
instance.__dict__[self.name] = value
class JSONDataMixin(object):
data_dict = JsonValue('data')
def update_json(self, name, json_value):
obj = getattr(self, name, None)
if obj is None:
raise KeyError(name)
obj.update(json_value)
setattr(self, name, obj)
使用时,只需要新增一列属性或者类中继承JSONDataMixin
即可,使用时,直接操作data_dict
或者其他自定义的名字。如下:
class MyTable(Base, JSONDataMixin):
id = Column(BigInteger, primary_key=True)
data = Column(Text)
或
class MyTable(Base):
id = Column(BigInteger, primary_key=True)
data = Column(Text)
data_dict = JsonValue('data')
但是此方法有个弊端,由于修改了data_dict之后,需要显示的调用obj.data_dict = obj.data_dict来触发(否则data值无改动),故用起来颇为麻烦。
为了解决上述弊端,所以有了如下的方案:
from sqlalchemy.types import TypeDecorator, VARCHAR
from sqlalchemy.ext.mutable import Mutable
import json
class JSONEncodedDict(TypeDecorator):
"""Representes an immutable structure as a json-encoded string"""
impl = VARCHAR
def process_bind_param(self, value, dialect):
if value:
value = json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value:
value = json.loads(value)
return value or dict()
class MutableDict(Mutable, dict):
@classmethod
def coerce(cls, key, value):
"""Convert plain dictionaries to MutableDict"""
if not isinstance(value, MutableDict):
if isinstance(value, dict):
return MutableDict(value)
return Mutable.coerce(key, value)
else:
return value
def __setitem__(self, key, value):
"""Detect dictionary set events and emit change events"""
dict.__setitem__(self, key, value)
self.changed()
def __delitem__(self, key):
"""Detect dictionary del events and emit change events"""
dict.__delitem__(self, key)
self.changed()
MutableDict.associate_with(JSONEncodedDict)
有了上面的方法,在代码中只需要用 JSONEncodedDict
当 Column来使用就行了,例如:
class MyTable(Base):
id = Column(BigInteger, primary_key=True)
data = Column(JSONEncodedDict)
这样就可以直接用data了,完美~