Django Model 序列化为 JSON 的自定义方案

今天早睡
今天早睡
发布时间:2026-10-13 | 6人浏览过
原创

本文介绍在 Django 中将 Model 序列化为 JSON 的自定义方法。针对官方序列化器格式复杂、对 List 和外键支持不佳等问题,通过继承 django.db.models.Model 并声明为 abstract,实现 getDict 和 toJSON 方法。该方法利用反射处理基本类型、日期及 ManyToMany 字段,生成简洁的 JSON 结构,并提供完整代码示例与使用指南。

本文介绍的开发环境如下:

  • Python 3.6.5
  • Django 2.0.4

fix(2018.5.19):近期发现 Django 的 Model 基类必须声明为 abstract,因此对原有代码添加了 abstract 声明,以避免误导读者。

在 Django 中,将 Model 类序列化为 JSON 通常有两种方式。

第一种是将 Model 类转换为字典,然后使用 json 库的 dumps 方法将其转换为 JSON 字符串。

第一种方法较为简单,建议直接参考官方文档获取详细信息。

虽然官方提供的方法通常较为稳定,但直接使用官方序列化器时可能会遇到一些问题:

生成的格式较为繁琐,示例如下,令人难以满意:

[
  {
    "pk": "4b678b301dfd8a4e0dad910de3ae245b",
    "model": "sessions.session",
    "fields": {
      "expire_date": "2013-01-16T08:16:59.844Z",
      ...
    }
  }
]

可以看到,其中 pk 表示默认主键,model 表示该对象所属的 Model 类型,而 fields 才包含对象的各个字段……这种结构确实令人难以评价。

  • 官方序列化器对列表(List)的支持不够理想。
  • 对于某些外键字段(包括 ManyToManyField 等)的支持也不够友好。
  • 甚至对于自身的 DateField 字段,官方序列化器也没有提供很好的支持。

列举了上述官方序列化器的缺点,当然,这些问题肯定有解决方案,但我确实不想再花费精力去折腾了。

因此,我提出以下解决方案:

  • 新建一个类 BaseModel,该类继承自官方的 django.db.models.Model。
  • 在 BaseModel 中声明一个方法,用于生成该对象对应的字典。
  • 利用该对象生成的字典来创建 JSON 数据。

生成对象字典的策略如下:

  • 通过反射机制获取该对象的所有字段名。
  • 根据字段名获取对应的字段 field 对象。
  • 如果 field 的类型是 int、float 或 str,则直接以 "字段名": 字段值 的形式放入字典中。
  • 若 field 的类型是 datetime 或 date,则使用 date 的方式进行处理,然后放入字典。
  • 若 field 的类型是 BaseModel,则调用该 field 的 getDict 方法递归获取该 field 对应的字典,然后放入字典中。
  • 若 field 的类型是 ManyToMany 类型,在具体场景中,我们使用这个 field 的 all 方法获取该 field 的所有对象,然后通过 getDict 方法将其放入到字典中。

以下是源码及使用方法。

from django.db import models
import json


class BaseModel(models.Model):
  class Meta:
    abstract = True

  # 返回self._meta.fields中没有的,但是又是需要的字段名的列表
  # 形如['name','type']
  def getMtMField(self):
    pass

  # 返回需要在json中忽略的字段名的列表
  # 形如['password']
  def getIgnoreList(self):
    pass

  def isAttrInstance(self, attr, clazz):
    return isinstance(getattr(self, attr), clazz)

  def getDict(self):
    fields = []
    for field in self._meta.fields:
      fields.append(field.name)

    d = {}
    import datetime
    for attr in fields:
      if isinstance(getattr(self, attr), datetime.datetime):
        d[attr] = getattr(self, attr).strftime('%Y-%m-%d %H:%M:%S')
      elif isinstance(getattr(self, attr), datetime.date):
        d[attr] = getattr(self, attr).strftime('%Y-%m-%d')
      # 特殊处理datetime的数据
      elif isinstance(getattr(self, attr), BaseModel):
        d[attr] = getattr(self, attr).getDict()
      # 递归生成BaseModel类的dict
      elif self.isAttrInstance(attr, int) or self.isAttrInstance(attr, float) \
          or self.isAttrInstance(attr, str):
        d[attr] = getattr(self, attr)
      # else:
      #   d[attr] = getattr(self, attr)

    mAttr = self.getMtMField()
    if mAttr is not None:
      for m in mAttr:
        if hasattr(self, m):
          attlist = getattr(self, m).all()
          l = []
          for attr in attlist:
            if isinstance(attr, BaseModel):
              l.append(attr.getDict())
            else:
              dic = attr.__dict__
              if '_state' in dic:
                dic.pop('_state')
              l.append(dic)
          d[m] = l
    # 由于ManyToMany类不能存在于_meat.fields,因而子类需要在getMtMFiled中返回这些字段
    if 'basemodel_ptr' in d:
      d.pop('basemodel_ptr')

    ignoreList = self.getIgnoreList()
    if ignoreList is not None:
      for m in ignoreList:
        if d.get(m) is not None:
          d.pop(m)
    # 移除不需要的字段
    return d

  def toJSON(self):
    import json
    return json.dumps(self.getDict(), ensure_ascii=False).encode('utf-8').decode()

使用方法如下:

所有的 models 类都继承 BaseModel 类,然后调用此类的 toJSON() 方法即可。

注意,不知为何,self._meta.fields 中没有包含 ManyToManyField 字段,因此需要重写 getMtMField 方法。例子如下:

class Book(BaseModel):
  name = models.CharField(max_length=50)
  authors = models.ManyToManyField(Author)
  publish = models.ForeignKey(Publisher, on_delete=models.SET_NULL, blank=True, null=True)
  page = models.IntegerField(default=0) # 页数
  introduction = models.CharField(max_length=500)
  bookType = models.ManyToManyField(BookType, null=True, blank=True)
  bookTag = models.ManyToManyField(BookTag, null=True, blank=True)
  evaluation = models.FloatField()
  coverUrl = models.CharField(max_length=100, null=True, blank=True)

  def getMtMField(self):
    return ['bookType', 'bookTag']

最终结果如下:

{
  "id":4,
  "name":"Django从入门到放弃",
  "page":123,
  "introduction":"introduction",
  "evaluation":1,
  "bookType":[
    {
      "id":1,
      "name":"类型"
    }
  ],
  "bookTag":[
    {
      "id":2,
      "name":"tag"
    }
  ]
}

后记。

源码中有所引用,即 getDict 方法中的第一个 for 循环,但懒得查找原链接,望见谅,特此声明。

  • 本人是 Python 新手,代码中可能存在不规范之处,望见谅。
  • 代码虽不精妙,但也希望能对你有所帮助。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表