SqlAlchemy ส่งผลต่อ JSON อย่างไร


192

Django มีการเรียงลำดับอัตโนมัติที่ดีของโมเดล ORM ที่ส่งคืนจาก DB เป็นรูปแบบ JSON

วิธีการซีเรียลไลซ์คิวรี SQLAlchemy เป็นผลลัพธ์ในรูปแบบ JSON

ฉันพยายามjsonpickle.encodeแต่มันเข้ารหัสวัตถุแบบสอบถามเอง ฉันพยายามjson.dumps(items)แต่มันกลับมา

TypeError: <Product('3', 'some name', 'some desc')> is not JSON serializable

มันยากจริงๆหรือไม่ที่จะทำให้ลำดับวัตถุ SQLAlchemy ORM เป็น JSON / XML เป็นลำดับ? ไม่มี serializer เริ่มต้นสำหรับมันหรือไม่? มันเป็นงานที่พบบ่อยมากในการเรียงลำดับผลลัพธ์การค้นหา ORM ในปัจจุบัน

สิ่งที่ฉันต้องการคือเพียงเพื่อส่งคืนการแสดงข้อมูล JSON หรือ XML ของผลการสืบค้น SQLAlchemy

SQLAlchemy วัตถุแบบสอบถามผลลัพธ์ในรูปแบบ JSON / XML จำเป็นต้องใช้ใน javascript datagird (JQGrid http://www.trirand.com/blog/ )


นี่เป็นวิธีแก้ปัญหาที่เหมาะกับฉัน ป้อนคำอธิบายลิงก์ที่นี่
octaedro

คำตอบ:


129

การใช้งานแบน

คุณสามารถใช้สิ่งนี้:

from sqlalchemy.ext.declarative import DeclarativeMeta

class AlchemyEncoder(json.JSONEncoder):

    def default(self, obj):
        if isinstance(obj.__class__, DeclarativeMeta):
            # an SQLAlchemy class
            fields = {}
            for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
                data = obj.__getattribute__(field)
                try:
                    json.dumps(data) # this will fail on non-encodable values, like other classes
                    fields[field] = data
                except TypeError:
                    fields[field] = None
            # a json-encodable dict
            return fields

        return json.JSONEncoder.default(self, obj)

จากนั้นแปลงเป็น JSON โดยใช้:

c = YourAlchemyClass()
print json.dumps(c, cls=AlchemyEncoder)

มันจะไม่สนใจฟิลด์ที่ไม่สามารถเข้ารหัสได้ (ตั้งค่าเป็น 'ไม่มี')

มันไม่ได้ขยายความสัมพันธ์โดยอัตโนมัติ (เนื่องจากอาจนำไปสู่การอ้างอิงตนเองและวนซ้ำตลอดไป)

การใช้งานแบบวนซ้ำและไม่วนซ้ำ

อย่างไรก็ตามหากคุณต้องการวนซ้ำตลอดไปคุณสามารถใช้:

from sqlalchemy.ext.declarative import DeclarativeMeta

def new_alchemy_encoder():
    _visited_objs = []

    class AlchemyEncoder(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj.__class__, DeclarativeMeta):
                # don't re-visit self
                if obj in _visited_objs:
                    return None
                _visited_objs.append(obj)

                # an SQLAlchemy class
                fields = {}
                for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
                    fields[field] = obj.__getattribute__(field)
                # a json-encodable dict
                return fields

            return json.JSONEncoder.default(self, obj)

    return AlchemyEncoder

แล้วเข้ารหัสวัตถุโดยใช้:

print json.dumps(e, cls=new_alchemy_encoder(), check_circular=False)

สิ่งนี้จะเข้ารหัสเด็กทุกคนและลูก ๆ ของพวกเขาและลูก ๆ ของพวกเขาทั้งหมด ... อาจเข้ารหัสฐานข้อมูลทั้งหมดของคุณได้ เมื่อถึงบางสิ่งที่ถูกเข้ารหัสมาก่อนมันจะเข้ารหัสเป็น 'ไม่มี'

การประยุกต์ใช้แบบเรียกซ้ำ, เวียนเป็นวงกลม,

ทางเลือกอื่นน่าจะดีกว่าคือการสามารถระบุเขตข้อมูลที่คุณต้องการขยาย:

def new_alchemy_encoder(revisit_self = False, fields_to_expand = []):
    _visited_objs = []

    class AlchemyEncoder(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj.__class__, DeclarativeMeta):
                # don't re-visit self
                if revisit_self:
                    if obj in _visited_objs:
                        return None
                    _visited_objs.append(obj)

                # go through each field in this SQLalchemy class
                fields = {}
                for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
                    val = obj.__getattribute__(field)

                    # is this field another SQLalchemy object, or a list of SQLalchemy objects?
                    if isinstance(val.__class__, DeclarativeMeta) or (isinstance(val, list) and len(val) > 0 and isinstance(val[0].__class__, DeclarativeMeta)):
                        # unless we're expanding this field, stop here
                        if field not in fields_to_expand:
                            # not expanding this field: set it to None and continue
                            fields[field] = None
                            continue

                    fields[field] = val
                # a json-encodable dict
                return fields

            return json.JSONEncoder.default(self, obj)

    return AlchemyEncoder

ตอนนี้คุณสามารถโทรหาด้วย:

print json.dumps(e, cls=new_alchemy_encoder(False, ['parents']), check_circular=False)

เมื่อต้องการขยายเขตข้อมูล SQLAlchemy ที่เรียกว่า 'parent' เท่านั้น


ที่ตอบสนองดี แต่ฉันได้รับ "ไม่สามารถเข้ารหัส" BaseQuery "เมื่อใดก็ตามที่พบความสัมพันธ์กับวิธีการที่ไม่ใช่แบนความคิดใด ๆ
Ben Kilah

1
@SashaB วิธีการเกี่ยวกับการกำหนดเป้าหมายอย่างละเอียดยิ่งขึ้นกับกรณีที่มีความสัมพันธ์ซ้ำแล้วซ้ำอีก? ตัวอย่างเช่นถ้าฉันมีonline_orderและaddressทั้งสองมีความสัมพันธ์กับuserแต่ยังมีความสัมพันธ์กับonline_order addressถ้าผมต้องการที่จะเป็นอันดับทั้งหมดนี้ผมต้องรวมaddressในfields_to_expandแต่ฉันไม่ต้องการที่จะเกินความจำเป็นอันดับaddressเนื่องจากความสัมพันธ์กับทั้งสองและuser online_order
Chrispy

2
@ BenKilah ให้ฉันเดาว่าคุณใช้ Flask-SqlAlchemy และแบบจำลองของคุณกำลังสืบทอดมาจาก db.Model ไม่ใช่ Base หากเป็นกรณีนี้ให้แก้ไขfor field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:เพื่อให้สามารถอ่านfor field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata' and not x.startswith('query')]:ได้ โปรดทราบว่าโซลูชันนี้จะป้องกันไม่ให้คุณมีทรัพย์สิน / ความสัมพันธ์กับชื่อ 'ข้อความค้นหา'
Pakman

เช่นเดียวกับฉัน แต่มีความซับซ้อนมากกว่า stackoverflow.com/questions/7102754/…
tyan

2
คุณสามารถใช้โซลูชันของฉันgithub.com/n0nSmoker/SQLAlchemy-serializer
n0nSmoker

272

คุณสามารถส่งออกวัตถุของคุณเป็นพจนานุกรม:

class User:
   def as_dict(self):
       return {c.name: getattr(self, c.name) for c in self.__table__.columns}

และจากนั้นคุณใช้User.as_dict()ในการทำให้เป็นอันดับวัตถุของคุณ

ตามที่อธิบายไว้ในการแปลงวัตถุแถว sqlalchemy เพื่อ dict หลาม


2
@charlax ฉันจะแก้ไข DateTime ได้อย่างไร ด้วยการใช้สิ่งนี้ฉันจะได้รับ 'datetime.datetime (2013, 3, 22, 16, 50, 11) ไม่ใช่ JSON ต่อเนื่อง' เมื่อฉันทำ json.dumps
Asken

1
มันเป็นความรับผิดชอบของJSONEncoderวัตถุ คุณสามารถ subclass เพื่อกำหนด encoder ของคุณเองสำหรับบางวัตถุรวมถึงวันที่และเวลา โปรดทราบว่าFlaskตัวอย่างเช่นสนับสนุนการเข้ารหัสวันที่และเวลาใน JSON นอกกรอบ (พร้อมรุ่นล่าสุด)
charlax

3
ถ้าคุณใช้เมธอด "declarative" ของ sqlalchemy คุณสามารถเพิ่มสิ่งนี้ลงในคลาส Base แบบกำหนดเองได้ - ซึ่งมันมีประโยชน์มากเพราะคุณสามารถเรียก my_orm_object.toDict () บนวัตถุ ORM ใด ๆ ที่คุณมี ในทำนองเดียวกันคุณสามารถกำหนดวิธีการ. toJSON () ซึ่งใช้วิธี toDict ของคุณและตัวเข้ารหัสที่กำหนดเองสำหรับการจัดการวันที่ blobs และอื่น ๆ
FredL

7
เพื่อสนับสนุน datetime ด้วย:return {c.name: unicode(getattr(self, c.name)) for c in self.__table__.columns}
Shoham

1
สิ่งนี้ไม่ทำงานหากตัวแปรคลาสของคุณไม่เหมือนกับชื่อคอลัมน์ของคุณ มีความคิดวิธีการรับชื่อคลาสแทนหรือไม่
James Burke

55

คุณสามารถแปลง RowProxy เป็น dict แบบนี้:

 d = dict(row.items())

จากนั้นทำอนุกรมให้กับ JSON (คุณจะต้องระบุตัวเข้ารหัสสำหรับสิ่งต่าง ๆ เช่นdatetimeค่า) ไม่ใช่เรื่องยากหากคุณต้องการเพียงหนึ่งระเบียน (และไม่ใช่ลำดับชั้นที่สมบูรณ์ของระเบียนที่เกี่ยวข้อง)

json.dumps([(dict(row.items())) for row in rs])

1
สิ่งนี้ใช้ได้กับเคียวรี sql แบบกำหนดเองของฉันกับ db.engine.connect () ในฐานะ con: rs = con.execute (sql)
JZ

1
มันง่ายและใช้งานได้มากกว่า อะไรคือข้อแตกต่างระหว่างคำตอบนี้กับคำตอบที่ยอมรับ?
Sundeep

46

ผมขอแนะนำให้ใช้ขนมหวาน จะช่วยให้คุณสร้าง serializers เพื่อเป็นตัวแทนของแบบจำลองของคุณด้วยการสนับสนุนความสัมพันธ์และวัตถุที่ซ้อนกัน

นี่คือตัวอย่างที่ถูกตัดทอนจากเอกสารของพวกเขา ใช้โมเดล ORM Author:

class Author(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    first = db.Column(db.String(80))
    last = db.Column(db.String(80))

เค้าร่างมาร์ชเมลโลว์สำหรับคลาสนั้นถูกสร้างเช่นนี้:

class AuthorSchema(Schema):
    id = fields.Int(dump_only=True)
    first = fields.Str()
    last = fields.Str()
    formatted_name = fields.Method("format_name", dump_only=True)

    def format_name(self, author):
        return "{}, {}".format(author.last, author.first)

... และใช้งานเช่นนี้:

author_schema = AuthorSchema()
author_schema.dump(Author.query.first())

... จะสร้างผลลัพธ์เช่นนี้:

{
        "first": "Tim",
        "formatted_name": "Peters, Tim",
        "id": 1,
        "last": "Peters"
}

มีลักษณะที่เต็มรูปแบบของพวกเขาขวด-SQLAlchemy ตัวอย่าง

ห้องสมุดที่เรียกว่าmarshmallow-sqlalchemyรวม SQLAlchemy และ marshmallow โดยเฉพาะ ในไลบรารีนั้นสกีมาสำหรับAuthorโมเดลที่อธิบายข้างต้นมีลักษณะดังนี้:

class AuthorSchema(ModelSchema):
    class Meta:
        model = Author

การรวมช่วยให้ประเภทของฟิลด์ที่จะอนุมานจากColumnประเภทSQLAlchemy

marshmallow-sqlalchemy ที่นี่


12
ฉันยังพบmarshmallow-sqlalchemy.readthedocs.io/en/latestซึ่งทำให้การสร้าง schema ง่ายขึ้น
Foo L

44

Python 3.7+ และ Flask 1.1+ สามารถใช้งานได้ในตัว แพ็คเกจดาต้าคลาสตัวได้

from dataclasses import dataclass
from datetime import datetime
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy


app = Flask(__name__)
db = SQLAlchemy(app)


@dataclass
class User(db.Model):
  id: int
  email: str

  id = db.Column(db.Integer, primary_key=True, auto_increment=True)
  email = db.Column(db.String(200), unique=True)


@app.route('/users/')
def users():
  users = User.query.all()
  return jsonify(users)  


if __name__ == "__main__":
  users = User(email="user1@gmail.com"), User(email="user2@gmail.com")
  db.create_all()
  db.session.add_all(users)
  db.session.commit()
  app.run()

/users/เส้นทางในขณะนี้จะกลับรายการของผู้ใช้

[
  {"email": "user1@gmail.com", "id": 1},
  {"email": "user2@gmail.com", "id": 2}
]

ปรับรุ่นที่เกี่ยวข้องให้เป็นแบบอัตโนมัติ

@dataclass
class Account(db.Model):
  id: int
  users: User

  id = db.Column(db.Integer)
  users = db.relationship(User)  # User model would need a db.ForeignKey field

การตอบสนองจาก jsonify(account)จะเป็นแบบนี้

{  
   "id":1,
   "users":[  
      {  
         "email":"user1@gmail.com",
         "id":1
      },
      {  
         "email":"user2@gmail.com",
         "id":2
      }
   ]
}

เขียนทับตัวเข้ารหัส JSON เริ่มต้น

from flask.json import JSONEncoder


class CustomJSONEncoder(JSONEncoder):
  "Add support for serializing timedeltas"

  def default(o):
    if type(o) == datetime.timedelta:
      return str(o)
    elif type(o) == datetime.datetime:
      return o.isoformat()
    else:
      return super().default(o)

app.json_encoder = CustomJSONEncoder      

1
ดูเหมือนง่าย ๆ แบบนี้ มันทำงานได้ดีหรือไม่?
Ender2050

คุณสามารถแปลงในพจนานุกรมของ JSON แยกวิเคราะห์ลงไปในรูปแบบที่ใช้อาร์กิวเมนต์คำหลักเอาออก:data = request.json['user']; user = User(**data)
ทอม

3
โปรดทราบว่าid: int = Columnจะใช้งานได้ แต่id = Columnจะไม่ดูเหมือนว่าคุณจะต้องประกาศการพิมพ์แบบคงที่สำหรับ json เพื่อทำให้เป็นอนุกรมฟิลด์มิฉะนั้นคุณจะได้รับ{}วัตถุว่างเปล่า
Ambroise Rabier

1
สิ่งนี้ใช้ได้กับฉันทำไมจึงไม่เป็นคำตอบที่ยอมรับได้ ฉันได้ลองเล่น app_context เป็นเวลาหลายชั่วโมงเพื่อที่จะได้ทำงานกับ Flask-Marshmallow
Nick Dat Le

1
ทำงานให้ฉันเช่นกัน โปรดทราบว่าถ้าคุณอยู่ใน Python 3.6 pipenv install dataclassesคุณจะต้องการเพียงแค่ติดตั้งแพคเกจ: แล้วมันก็ใช้ได้ดี
AleksandrH

14

แพ็คเกจFlask-JsonToolsมีการติดตั้งคลาสฐานJsonSerializableBaseสำหรับรุ่นของคุณ

การใช้งาน:

from sqlalchemy.ext.declarative import declarative_base
from flask.ext.jsontools import JsonSerializableBase

Base = declarative_base(cls=(JsonSerializableBase,))

class User(Base):
    #...

ตอนนี้ตัวUserแบบสามารถต่อเนื่องกันได้อย่างน่าอัศจรรย์

หากกรอบงานของคุณไม่ใช่ Flask คุณสามารถหยิบโค้ดได้


2
วิธีนี้ช่วยแก้ปัญหาเพียงครึ่งเดียวเนื่องจากจะทำให้เป็นอนุกรมแถวเดียวเท่านั้น จะทำให้ผลการสืบค้นทั้งหมดเป็นลำดับได้อย่างไร?
Steve Bennett

@SteveBennett ใช้ jsontools ' jsonapiเพื่อเข้ารหัสการตอบสนอง ที่จะเข้ารหัสวัตถุกลับโดยอัตโนมัติ
Tjorriemorrie

ฉันมีรูปแบบ sqlalchemy ง่ายมากและฉันได้รับ: TypeError: <ORM.State วัตถุที่ 0x03577A50> ไม่ใช่ JSON ต่อเนื่องได้
Matej

1
ในที่สุดมันก็ทำงานโดยการโทร __json __ () อย่างชัดเจนบนวัตถุโมเดลของฉัน: ส่งคืน my_object .__ json __ ()
Matej

ไลบรารีไม่ทำงานกับ Flask 1.0 ขึ้นไปเนื่องจากimport flask.ext.whateverไม่รองรับ Flask 1.0 อีกต่อไป
Adarsh ​​Madrecha

14

เพื่อเหตุผลด้านความปลอดภัยคุณไม่ควรส่งคืนฟิลด์ทุกรุ่น ฉันชอบที่จะเลือกพวกเขา

ตอนนี้การเข้ารหัส json ของ Flask รองรับ UUID, datetime และความสัมพันธ์ (และเพิ่มqueryและquery_classสำหรับdb.Modelคลาสflask_sqlalchemy ) ฉันได้อัปเดตโปรแกรมเปลี่ยนไฟล์ดังนี้:

app / json_encoder.py

    from sqlalchemy.ext.declarative import DeclarativeMeta
    from flask import json


    class AlchemyEncoder(json.JSONEncoder):
        def default(self, o):
            if isinstance(o.__class__, DeclarativeMeta):
                data = {}
                fields = o.__json__() if hasattr(o, '__json__') else dir(o)
                for field in [f for f in fields if not f.startswith('_') and f not in ['metadata', 'query', 'query_class']]:
                    value = o.__getattribute__(field)
                    try:
                        json.dumps(value)
                        data[field] = value
                    except TypeError:
                        data[field] = None
                return data
            return json.JSONEncoder.default(self, o)

app/__init__.py

# json encoding
from app.json_encoder import AlchemyEncoder
app.json_encoder = AlchemyEncoder

ด้วยวิธีนี้ฉันสามารถเลือกที่จะเพิ่ม__json__คุณสมบัติที่ส่งกลับรายการของฟิลด์ที่ฉันต้องการเข้ารหัส:

app/models.py

class Queue(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    song_id = db.Column(db.Integer, db.ForeignKey('song.id'), unique=True, nullable=False)
    song = db.relationship('Song', lazy='joined')
    type = db.Column(db.String(20), server_default=u'audio/mpeg')
    src = db.Column(db.String(255), nullable=False)
    created_at = db.Column(db.DateTime, server_default=db.func.now())
    updated_at = db.Column(db.DateTime, server_default=db.func.now(), onupdate=db.func.now())

    def __init__(self, song):
        self.song = song
        self.src = song.full_path

    def __json__(self):
        return ['song', 'src', 'type', 'created_at']

ฉันเพิ่ม @ jsonapi ในมุมมองของฉันคืนรายการผลลัพธ์แล้วผลลัพธ์ของฉันจะเป็นดังนี้:

[

{

    "created_at": "Thu, 23 Jul 2015 11:36:53 GMT",
    "song": 

        {
            "full_path": "/static/music/Audioslave/Audioslave [2002]/1 Cochise.mp3",
            "id": 2,
            "path_name": "Audioslave/Audioslave [2002]/1 Cochise.mp3"
        },
    "src": "/static/music/Audioslave/Audioslave [2002]/1 Cochise.mp3",
    "type": "audio/mpeg"
}

]

สวย! พิสูจน์อีกครั้งว่าบางครั้งคุณไม่จำเป็นต้องมีชุดไขมันสำหรับงานเล็ก ๆ น้อย ๆ ที่โง่ ๆ - การเรียนรู้ DSL อาจทำได้ยากกว่าการทำแบบ "ยาก" ฉันดูแพ็คเกจ JSON และ REST มากมายก่อนที่จะลงจอดที่นี่ ทรูนี้ยังคงต้องใช้แพคเกจflask_jsontools (เพื่อเพิ่ม@jsonapiการ@app.routeในviews.pyฯลฯ ) แต่ฉันรักเรียบง่ายของมัน ฉันคิดว่ามันถูกเพิ่ม Flask datetime แต่ไม่ใช่วันที่ดังนั้นฉันจึงเพิ่มตัวเองในjson_encoder.py : value=...^ if isinstance(value, date):^ data[field] = datetime.combine(value, time.min).isoformat()^ else:^try:...
juanitogan

10

คุณสามารถใช้วิปัสสนาของ SqlAlchemy เช่นนี้:

mysql = SQLAlchemy()
from sqlalchemy import inspect

class Contacts(mysql.Model):  
    __tablename__ = 'CONTACTS'
    id = mysql.Column(mysql.Integer, primary_key=True)
    first_name = mysql.Column(mysql.String(128), nullable=False)
    last_name = mysql.Column(mysql.String(128), nullable=False)
    phone = mysql.Column(mysql.String(128), nullable=False)
    email = mysql.Column(mysql.String(128), nullable=False)
    street = mysql.Column(mysql.String(128), nullable=False)
    zip_code = mysql.Column(mysql.String(128), nullable=False)
    city = mysql.Column(mysql.String(128), nullable=False)
    def toDict(self):
        return { c.key: getattr(self, c.key) for c in inspect(self).mapper.column_attrs }

@app.route('/contacts',methods=['GET'])
def getContacts():
    contacts = Contacts.query.all()
    contactsArr = []
    for contact in contacts:
        contactsArr.append(contact.toDict()) 
    return jsonify(contactsArr)

@app.route('/contacts/<int:id>',methods=['GET'])
def getContact(id):
    contact = Contacts.query.get(id)
    return jsonify(contact.toDict())

รับแรงบันดาลใจจากคำตอบที่นี่: แปลงวัตถุแถว sqlalchemy เป็นพจน์ ธ


5

คำอธิบายรายละเอียดเพิ่มเติม ในแบบจำลองของคุณเพิ่ม:

def as_dict(self):
       return {c.name: str(getattr(self, c.name)) for c in self.__table__.columns}

str()สำหรับหลาม 3 ดังนั้นหากใช้งูหลาม 2 unicode()การใช้งาน มันจะช่วยกำจัดวันที่ออก คุณสามารถลบออกได้หากไม่จัดการกับสิ่งเหล่านั้น

ตอนนี้คุณสามารถสืบค้นฐานข้อมูลเช่นนี้

some_result = User.query.filter_by(id=current_user.id).first().as_dict()

First()จำเป็นเพื่อหลีกเลี่ยงข้อผิดพลาดแปลก ๆ as_dict()จะเลิกทำการประเมินผล หลังจากดีซีเรียลไลเซชันมันพร้อมที่จะถูกเปลี่ยนเป็น json

jsonify(some_result)

3

มันไม่ได้ตรงไปตรงมามาก ฉันเขียนโค้ดเพื่อทำสิ่งนี้ ฉันยังคงทำงานต่อไปและใช้กรอบงาน MochiKit มันแปลวัตถุประกอบระหว่าง Python และ Javascript โดยใช้พร็อกซีและตัวแปลง JSON ที่ลงทะเบียนแล้ว

ด้านเบราว์เซอร์สำหรับวัตถุฐานข้อมูลdb.js มันต้องการพื้นฐานหลามแหล่งพร็อกซี่ในproxy.js

บนฝั่งหลามมีโมดูลพร็อกซีฐานอยู่ แล้วในที่สุดเข้ารหัสวัตถุ sqlalchemy ในwebserver.py นอกจากนี้ยังขึ้นอยู่กับตัวแยกข้อมูลเมตาที่พบในไฟล์models.py


ค่อนข้างซับซ้อนจากแวบแรก ... สิ่งที่ฉันต้องการ - คือการรับผลการสืบค้น SQLAlchemy วัตถุในรูปแบบ JSON / XML เพื่อใช้ใน javascript datagird (JQGrid trirand.com/blog )
Zelid

บางครั้งปัญหามีความซับซ้อนมากกว่าที่คุณพบในครั้งแรก ... สิ่งนี้จัดการกับวัตถุที่ส่งคืนเป็นคีย์ต่างประเทศและพยายามหลีกเลี่ยงการวนซ้ำแบบไม่รู้จบที่เกิดขึ้นกับความสัมพันธ์ที่ซ้อนกันอย่างล้ำลึก อย่างไรก็ตามคุณอาจจะเขียนแบบสอบถามแบบกำหนดเองบางอย่างที่ส่งคืนชนิดฐานเท่านั้นและทำให้เป็นอนุกรมด้วย simplejson โดยตรง
Keith

1
ใช่ฉันจะไปกับการสอบถาม dicts โดยใช้ SQLAlchemy จริงๆและจะใช้ประโยชน์จาก ORM ในการดำเนินการบันทึก / อัปเดตเท่านั้น
Zelid

3

ในขณะที่คำถามเดิมกลับมาชั่วขณะจำนวนคำตอบที่นี่ (และประสบการณ์ของฉัน) แนะนำว่าเป็นคำถามที่ไม่สำคัญกับวิธีการที่แตกต่างกันมากมายของความซับซ้อนที่แตกต่างกับการแลกเปลี่ยนที่แตกต่างกัน

นั่นเป็นเหตุผลที่ฉันสร้างไลบรารีSQLAthanorที่ขยาย ORM ของ SQLAlchemy ด้วยการสนับสนุน serialization / de-serialization ที่สามารถกำหนดค่าได้ซึ่งคุณอาจต้องการดู

ห้องสมุดรองรับ:

  • Python 2.7, 3.4, 3.5 และ 3.6
  • SQLAlchemy เวอร์ชั่น 0.9 และสูงกว่า
  • การทำให้เป็นอนุกรม / การทำให้เป็นอนุกรมถึง / จาก JSON, CSV, YAML และ Python dict
  • การทำให้เป็นอนุกรม / การทำให้เป็นอนุกรมของคอลัมน์ / คุณลักษณะความสัมพันธ์คุณสมบัติไฮบริดและพร็อกซีเชื่อมโยง
  • การเปิดใช้งานและปิดใช้งานการทำให้เป็นอนุกรมสำหรับรูปแบบและคอลัมน์ / ความสัมพันธ์ / คุณลักษณะเฉพาะ (เช่นคุณต้องการสนับสนุนค่าขาเข้า passwordแต่ไม่รวมขาออก )
  • การประมวลผลค่าก่อนเป็นลำดับก่อนและหลังการดีซีเรียลไลซ์เซชั่น
  • ไวยากรณ์ที่ค่อนข้างตรงไปตรงมาซึ่งเป็นทั้ง Pythonic และสอดคล้องกับแนวทางของ SQLAlchemy อย่างราบรื่น

คุณสามารถตรวจสอบเอกสารที่ครอบคลุม (ฉันหวังว่า!) ที่นี่: https://sqlathanor.readthedocs.io/th/latest

หวังว่านี่จะช่วยได้!


2

การทำให้เป็นอันดับแบบกำหนดเองและ

"from_json" (วิธีการเรียน) สร้างวัตถุรุ่นตามข้อมูล json

"deserialize"สามารถเรียกใช้ได้เฉพาะบนอินสแตนซ์และรวมข้อมูลทั้งหมดจาก json เข้าในอินสแตนซ์ของโมเดล

"เป็นอันดับ" - เป็นอันดับซ้ำ

จำเป็นต้องใช้คุณสมบัติ__write_only__เพื่อกำหนดคุณสมบัติการเขียนเท่านั้น (เช่น "password_hash")

class Serializable(object):
    __exclude__ = ('id',)
    __include__ = ()
    __write_only__ = ()

    @classmethod
    def from_json(cls, json, selfObj=None):
        if selfObj is None:
            self = cls()
        else:
            self = selfObj
        exclude = (cls.__exclude__ or ()) + Serializable.__exclude__
        include = cls.__include__ or ()
        if json:
            for prop, value in json.iteritems():
                # ignore all non user data, e.g. only
                if (not (prop in exclude) | (prop in include)) and isinstance(
                        getattr(cls, prop, None), QueryableAttribute):
                    setattr(self, prop, value)
        return self

    def deserialize(self, json):
        if not json:
            return None
        return self.__class__.from_json(json, selfObj=self)

    @classmethod
    def serialize_list(cls, object_list=[]):
        output = []
        for li in object_list:
            if isinstance(li, Serializable):
                output.append(li.serialize())
            else:
                output.append(li)
        return output

    def serialize(self, **kwargs):

        # init write only props
        if len(getattr(self.__class__, '__write_only__', ())) == 0:
            self.__class__.__write_only__ = ()
        dictionary = {}
        expand = kwargs.get('expand', ()) or ()
        prop = 'props'
        if expand:
            # expand all the fields
            for key in expand:
                getattr(self, key)
        iterable = self.__dict__.items()
        is_custom_property_set = False
        # include only properties passed as parameter
        if (prop in kwargs) and (kwargs.get(prop, None) is not None):
            is_custom_property_set = True
            iterable = kwargs.get(prop, None)
        # loop trough all accessible properties
        for key in iterable:
            accessor = key
            if isinstance(key, tuple):
                accessor = key[0]
            if not (accessor in self.__class__.__write_only__) and not accessor.startswith('_'):
                # force select from db to be able get relationships
                if is_custom_property_set:
                    getattr(self, accessor, None)
                if isinstance(self.__dict__.get(accessor), list):
                    dictionary[accessor] = self.__class__.serialize_list(object_list=self.__dict__.get(accessor))
                # check if those properties are read only
                elif isinstance(self.__dict__.get(accessor), Serializable):
                    dictionary[accessor] = self.__dict__.get(accessor).serialize()
                else:
                    dictionary[accessor] = self.__dict__.get(accessor)
        return dictionary

2

นี่คือโซลูชันที่ให้คุณเลือกความสัมพันธ์ที่คุณต้องการรวมไว้ในผลลัพธ์ของคุณได้ลึกที่สุดเท่าที่คุณต้องการ หมายเหตุ: นี่เป็นการเขียนใหม่ทั้งหมดโดยใช้ dict / str เป็น arg แทนที่จะเป็นรายการ แก้ไขบางสิ่ง ..

def deep_dict(self, relations={}):
    """Output a dict of an SA object recursing as deep as you want.

    Takes one argument, relations which is a dictionary of relations we'd
    like to pull out. The relations dict items can be a single relation
    name or deeper relation names connected by sub dicts

    Example:
        Say we have a Person object with a family relationship
            person.deep_dict(relations={'family':None})
        Say the family object has homes as a relation then we can do
            person.deep_dict(relations={'family':{'homes':None}})
            OR
            person.deep_dict(relations={'family':'homes'})
        Say homes has a relation like rooms you can do
            person.deep_dict(relations={'family':{'homes':'rooms'}})
            and so on...
    """
    mydict =  dict((c, str(a)) for c, a in
                    self.__dict__.items() if c != '_sa_instance_state')
    if not relations:
        # just return ourselves
        return mydict

    # otherwise we need to go deeper
    if not isinstance(relations, dict) and not isinstance(relations, str):
        raise Exception("relations should be a dict, it is of type {}".format(type(relations)))

    # got here so check and handle if we were passed a dict
    if isinstance(relations, dict):
        # we were passed deeper info
        for left, right in relations.items():
            myrel = getattr(self, left)
            if isinstance(myrel, list):
                mydict[left] = [rel.deep_dict(relations=right) for rel in myrel]
            else:
                mydict[left] = myrel.deep_dict(relations=right)
    # if we get here check and handle if we were passed a string
    elif isinstance(relations, str):
        # passed a single item
        myrel = getattr(self, relations)
        left = relations
        if isinstance(myrel, list):
            mydict[left] = [rel.deep_dict(relations=None)
                                 for rel in myrel]
        else:
            mydict[left] = myrel.deep_dict(relations=None)

    return mydict

ดังนั้นสำหรับตัวอย่างโดยใช้บุคคล / ครอบครัว / บ้านพัก / ห้อง ... เปลี่ยนเป็น json ทั้งหมดที่คุณต้องการคือ

json.dumps(person.deep_dict(relations={'family':{'homes':'rooms'}}))

นี่เป็นเรื่องปกติที่ฉันคิดว่าจะใส่ในคลาสพื้นฐานของคุณเพื่อให้วัตถุทั้งหมดมี ฉันจะปล่อยให้การเข้ารหัส json กับคุณ ...
35432

โปรดทราบว่ารุ่นนี้จะได้รับความสัมพันธ์ของรายการทั้งหมดดังนั้นจึงควรระมัดระวังในการจัดหาความสัมพันธ์กับรายการจำนวนมาก ...
239456

1
def alc2json(row):
    return dict([(col, str(getattr(row,col))) for col in row.__table__.columns.keys()])

ฉันคิดว่าฉันจะเล่นรหัสกอล์ฟเล็กน้อยกับอันนี้

FYI: ฉันกำลังใช้automap_baseเนื่องจากเรามีสคีมาที่ออกแบบแยกต่างหากตามความต้องการทางธุรกิจ ฉันเพิ่งเริ่มใช้ SQLAlchemy วันนี้ แต่เอกสารระบุว่า automap_base เป็นส่วนขยายของ declarative_base ซึ่งดูเหมือนจะเป็นกระบวนทัศน์ทั่วไปใน SQLAlchemy ORM ดังนั้นฉันจึงเชื่อว่าสิ่งนี้จะทำงานได้

มันไม่แปลกใจเลยที่จะมีคีย์ต่างประเทศต่อวิธีแก้ปัญหาของTjorriemorrieแต่มันจับคู่คอลัมน์กับค่าและจัดการชนิด Python โดย str () โดยใช้ค่าคอลัมน์ ค่าของเราประกอบด้วย Python datetime.time และ decimalDecimal ผลลัพธ์ประเภทคลาสดังนั้นจึงทำให้งานเสร็จ

หวังว่านี่จะช่วยผู้คนที่เดินผ่านไปมา!


1

ฉันรู้ว่านี่เป็นโพสต์ที่ค่อนข้างเก่า ฉันใช้โซลูชันที่ได้รับจาก @SashaB และแก้ไขตามความต้องการของฉัน

ฉันได้เพิ่มสิ่งต่อไปนี้ลงใน:

  1. รายการละเว้นฟิลด์: รายการของฟิลด์ที่จะถูกละเว้นในขณะที่ซีเรียลไลซ์
  2. รายการแทนที่ฟิลด์: พจนานุกรมที่มีชื่อฟิลด์ที่จะแทนที่ด้วยค่าในขณะที่ซีเรียลไลซ์
  3. วิธีการลบและ BaseQuery รับอนุกรม

รหัสของฉันเป็นดังนี้:

def alchemy_json_encoder(revisit_self = False, fields_to_expand = [], fields_to_ignore = [], fields_to_replace = {}):
   """
   Serialize SQLAlchemy result into JSon
   :param revisit_self: True / False
   :param fields_to_expand: Fields which are to be expanded for including their children and all
   :param fields_to_ignore: Fields to be ignored while encoding
   :param fields_to_replace: Field keys to be replaced by values assigned in dictionary
   :return: Json serialized SQLAlchemy object
   """
   _visited_objs = []
   class AlchemyEncoder(json.JSONEncoder):
      def default(self, obj):
        if isinstance(obj.__class__, DeclarativeMeta):
            # don't re-visit self
            if revisit_self:
                if obj in _visited_objs:
                    return None
                _visited_objs.append(obj)

            # go through each field in this SQLalchemy class
            fields = {}
            for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata' and x not in fields_to_ignore]:
                val = obj.__getattribute__(field)
                # is this field method defination, or an SQLalchemy object
                if not hasattr(val, "__call__") and not isinstance(val, BaseQuery):
                    field_name = fields_to_replace[field] if field in fields_to_replace else field
                    # is this field another SQLalchemy object, or a list of SQLalchemy objects?
                    if isinstance(val.__class__, DeclarativeMeta) or \
                            (isinstance(val, list) and len(val) > 0 and isinstance(val[0].__class__, DeclarativeMeta)):
                        # unless we're expanding this field, stop here
                        if field not in fields_to_expand:
                            # not expanding this field: set it to None and continue
                            fields[field_name] = None
                            continue

                    fields[field_name] = val
            # a json-encodable dict
            return fields

        return json.JSONEncoder.default(self, obj)
   return AlchemyEncoder

หวังว่ามันจะช่วยให้ใครบางคน!


1

ใช้serializerในตัวใน SQLAlchemy:

from sqlalchemy.ext.serializer import loads, dumps
obj = MyAlchemyObject()
# serialize object
serialized_obj = dumps(obj)

# deserialize object
obj = loads(serialized_obj)

session.expunge(obj)หากคุณกำลังโอนวัตถุระหว่างการประชุมอย่าลืมถอดวัตถุจากเซสชันปัจจุบันใช้ session.add(obj)การแนบอีกครั้งเพียงแค่ทำ


ดี แต่ไม่แปลงเป็น JSON
blakev

2
สำหรับ JSON 'อนุกรม' ตรวจสอบขนมหวาน-sqlalchemy ทางออกที่ดีที่สุดแน่นอนเมื่อคุณเปิดเผยวัตถุให้ลูกค้า marshmallow-sqlalchemy.readthedocs.io
chribsen

โมดูล serializer เหมาะสำหรับโครงสร้างแบบสอบถามเท่านั้น ไม่จำเป็นสำหรับ: อินสแตนซ์ของคลาสที่ผู้ใช้กำหนด สิ่งเหล่านี้ไม่มีการอ้างอิงไปยังเอนจินการประชุมหรือการสร้างนิพจน์ในกรณีทั่วไปและสามารถต่อเนื่องได้โดยตรง
thomasd

1

รหัสต่อไปนี้จะทำให้เป็นผล sqlalchemy เพื่อ json

import json
from collections import OrderedDict


def asdict(self):
    result = OrderedDict()
    for key in self.__mapper__.c.keys():
        if getattr(self, key) is not None:
            result[key] = str(getattr(self, key))
        else:
            result[key] = getattr(self, key)
    return result


def to_array(all_vendors):
    v = [ ven.asdict() for ven in all_vendors ]
    return json.dumps(v) 

โทรสนุก

def all_products():
    all_products = Products.query.all()
    return to_array(all_products)

1

AlchemyEncoder ยอดเยี่ยม แต่บางครั้งก็ล้มเหลวด้วยค่าทศนิยม นี่คือตัวเข้ารหัสที่ได้รับการปรับปรุงซึ่งแก้ปัญหาทศนิยม -

class AlchemyEncoder(json.JSONEncoder):
# To serialize SQLalchemy objects 
def default(self, obj):
    if isinstance(obj.__class__, DeclarativeMeta):
        model_fields = {}
        for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
            data = obj.__getattribute__(field)
            print data
            try:
                json.dumps(data)  # this will fail on non-encodable values, like other classes
                model_fields[field] = data
            except TypeError:
                model_fields[field] = None
        return model_fields
    if isinstance(obj, Decimal):
        return float(obj)
    return json.JSONEncoder.default(self, obj)

1

เมื่อใช้ sqlalchemy เพื่อเชื่อมต่อกับ db ฉันนี่เป็นทางออกที่ง่ายซึ่งสามารถกำหนดค่าได้สูง ใช้แพนด้า

import pandas as pd
import sqlalchemy

#sqlalchemy engine configuration
engine = sqlalchemy.create_engine....

def my_function():
  #read in from sql directly into a pandas dataframe
  #check the pandas documentation for additional config options
  sql_DF = pd.read_sql_table("table_name", con=engine)

  # "orient" is optional here but allows you to specify the json formatting you require
  sql_json = sql_DF.to_json(orient="index")

  return sql_json

1
step1:
class CNAME:
   ...
   def as_dict(self):
       return {item.name: getattr(self, item.name) for item in self.__table__.columns}

step2:
list = []
for data in session.query(CNAME).all():
    list.append(data.as_dict())

step3:
return jsonify(list)

3
การทิ้งโค้ดโดยไม่มีคำอธิบายใด ๆ นั้นไม่ค่อยมีประโยชน์ Stack Overflow เป็นเรื่องเกี่ยวกับการเรียนรู้ไม่ได้ให้ตัวอย่างเพื่อคัดลอกและวางสุ่มสี่สุ่มห้า โปรดแก้ไขคำถามของคุณและอธิบายวิธีการทำงานได้ดีกว่าสิ่งที่ OP ให้ไว้
Chris

0

ภายใต้ Flask จะทำงานและจัดการกับเขตข้อมูล datatime เปลี่ยนเขตข้อมูลชนิด
'time': datetime.datetime(2018, 3, 22, 15, 40)ลงใน
"time": "2018-03-22 15:40:00":

obj = {c.name: str(getattr(self, c.name)) for c in self.__table__.columns}

# This to get the JSON body
return json.dumps(obj)

# Or this to get a response object
return jsonify(obj)

0

โช้ก serializer ในตัวที่มี utf-8 ไม่สามารถถอดรหัสไบต์เริ่มต้นที่ไม่ถูกต้องสำหรับอินพุตบางตัว ฉันกลับไปที่:

def row_to_dict(row):
    temp = row.__dict__
    temp.pop('_sa_instance_state', None)
    return temp


def rows_to_list(rows):
    ret_rows = []
    for row in rows:
        ret_rows.append(row_to_dict(row))
    return ret_rows


@website_blueprint.route('/api/v1/some/endpoint', methods=['GET'])
def some_api():
    '''
    /some_endpoint
    '''
    rows = rows_to_list(SomeModel.query.all())
    response = app.response_class(
        response=jsonplus.dumps(rows),
        status=200,
        mimetype='application/json'
    )
    return response

0

บางทีคุณอาจใช้ชั้นเรียนแบบนี้

from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy import Table


class Custom:
    """Some custom logic here!"""

    __table__: Table  # def for mypy

    @declared_attr
    def __tablename__(cls):  # pylint: disable=no-self-argument
        return cls.__name__  # pylint: disable= no-member

    def to_dict(self) -> Dict[str, Any]:
        """Serializes only column data."""
        return {c.name: getattr(self, c.name) for c in self.__table__.columns}

Base = declarative_base(cls=Custom)

class MyOwnTable(Base):
    #COLUMNS!

เมื่อวัตถุทั้งหมดมีto_dictวิธีการ


0

ในขณะที่ใช้วัตถุดิบ sql และวัตถุที่ไม่ได้กำหนดการใช้cursor.descriptionดูเหมือนจะได้รับสิ่งที่ฉันกำลังมองหา:

with connection.cursor() as cur:
    print(query)
    cur.execute(query)
    for item in cur.fetchall():
        row = {column.name: item[i] for i, column in enumerate(cur.description)}
        print(row)

-2

การใช้พจนานุกรมของฉัน (มากเกินไป?):

def serialize(_query):
    #d = dictionary written to per row
    #D = dictionary d is written to each time, then reset
    #Master = dictionary of dictionaries; the id Key (int, unique from database) 
    from D is used as the Key for the dictionary D entry in Master
    Master = {}
    D = {}
    x = 0
    for u in _query:
        d = u.__dict__
        D = {}
        for n in d.keys():
           if n != '_sa_instance_state':
                    D[n] = d[n]
        x = d['id']
        Master[x] = D
    return Master

การรันกับ flask (รวมถึง jsonify) และ flask_sqlalchemy เพื่อพิมพ์เอาต์พุตเป็น JSON

เรียกใช้ฟังก์ชันด้วย jsonify (serialize ())

ทำงานร่วมกับแบบสอบถาม SQLAlchemy ทั้งหมดที่ฉันได้ลองไปแล้ว (เรียกใช้ SQLite3)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.