skip to content

What does Django's django.core.serializers module provide, and how does it differ from a Django REST Framework serializer?

level: middleimportance: nice to knowfreq 22%

answer

  1. the engine under dumpdata
  2. model, pk, fields
  3. serialize(), deserialize()
  4. DeserializedObject wraps an unsaved instance

basics

~20 s

django.core.serializers turns model instances into Django's fixture format (JSON, JSONL, XML, YAML) and back, powering dumpdata and loaddata. It has a fixed model/pk/fields shape and no validation, unlike DRF serializers, which define API representations and validate input.

solid answer

~40 s

`django.core.serializers` is the engine behind `dumpdata` and `loaddata`. `serializers.serialize("json", queryset, fields=[...])` returns a string in Django's fixed shape: one object per row with `model`, `pk` and `fields`, the primary key always outside `fields`. `serializers.deserialize("json", data)` yields `DeserializedObject` wrappers around unsaved instances; calling `.save()` writes each one raw, bypassing the model's `save()` and running no validation. Built-in formats are `json`, `jsonl`, `xml` and `yaml` (with PyYAML), and `SERIALIZATION_MODULES` registers more. A Django REST Framework serializer is a different tool: you declare the fields of an API representation, it validates incoming data and creates or updates instances. Django's serializers are for moving rows between databases, not for shaping an API.

code

python · 10 lines
python
from django.core import serializers

from geo.models import Currency

data = serializers.serialize("json", Currency.objects.all(), fields=["code", "name"])

for wrapped in serializers.deserialize("json", data):
    currency = wrapped.object  # unsaved Currency instance
    if currency.code != "XXX":
        wrapped.save()  # raw save: no save() override, no validation

go deeper

for a junior

Recall serialize() and deserialize(), the model/pk/fields shape, and that this is what dumpdata and loaddata use.

for a middle

Explain DeserializedObject, the raw save it performs without validation, and the multi-table inheritance and subset-of-fields pitfalls.

for a senior

Be able to write a one-off import or migration script on this API, and explain why returning its output from a view is a security and design smell.

for a principal

Decide which data-exchange formats a team standardises on between environments and services, and keep fixture formats out of public contracts.

## The module and its two functions `django.core.serializers` converts model instances to and from text. It is what `dumpdata` and `loaddata` call under the hood, and you can call it directly: - `serializers.serialize(format, queryset_or_iterable, **options)` returns a string. Useful options are `fields` (only these fields), `indent`, `use_natural_foreign_keys` and `use_natural_primary_keys`, and `stream` to write to a file-like object. - `serializers.deserialize(format, stream_or_string, **options)` returns an **iterator** of `DeserializedObject` instances. Formats are looked up by name: `serializers.get_serializer("json")` returns the class, and unknown names raise `SerializerDoesNotExist`. ## The output shape is fixed Every format writes the same logical structure: for each object its `model` label (`geo.currency`), its `pk`, and a `fields` mapping. The primary key is **always** written as `pk` and never inside `fields`, even when you pass `fields=[...]`. Foreign keys are written as the related id (or natural key), and many-to-many relations as a list on the model that declares them. Two consequences that interviewers like: 1. With **multi-table inheritance**, serializing the child only writes the child's **local** fields. You must serialize the parent rows too, or the data cannot be reloaded. 2. Serializing a **subset** of fields can produce data that cannot be deserialized and saved, because required columns are missing. ## Formats | Identifier | Notes | |---|---| | `json` | Default for `dumpdata`; uses `DjangoJSONEncoder`, which handles dates, times, `Decimal` and `UUID`. | | `jsonl` | One JSON object per line; convenient for large dumps. | | `xml` | A simple XML dialect with `<django-objects>` and `<object>` elements. Since 6.1 the deserializer raises `SuspiciousOperation` on unexpected nested tags. | | `yaml` | Available only when PyYAML is installed. | There is also an internal `python` serializer (plain dicts) used by the others; it is not offered as a public format. Custom formats are registered through the `SERIALIZATION_MODULES` setting. ## Deserializing and saving A `DeserializedObject` holds `.object`, an **unsaved** model instance, plus any many-to-many data. Calling `.save()`: - writes the instance with `Model.save_base(raw=True)`, so the model's `save()` override does not run and `pre_save`/`post_save` receivers see `raw=True`; - then sets many-to-many relations; - does **not** call `full_clean()`; nothing is validated. If the serialized `pk` is missing or null, a new row is inserted, unless the model supports natural keys and an existing row matches. You may also inspect or modify `.object` before saving, which is how custom import scripts filter data. ## Versus a Django REST Framework serializer | Aspect | `django.core.serializers` | DRF `Serializer` / `ModelSerializer` | |---|---|---| | Purpose | dump and restore rows | define an API representation | | Shape | fixed `model` / `pk` / `fields` | whatever fields you declare | | Input validation | none | field and object validation | | Writing | raw save, bypasses `save()` | `create()` / `update()`, calling normal saves | | Typical caller | `dumpdata`, `loaddata`, scripts | API views | ## Custom formats The format registry is extensible. The `SERIALIZATION_MODULES` setting (not defined by default) maps a format name to a module path; the module provides a `Serializer` class and, since Django 5.2, a `Deserializer` **class** rather than a function, which makes subclassing an existing format easier. A registered format becomes available to `serialize()`, `deserialize()`, `dumpdata --format` and `loaddata` like the built-in ones. Formats are selected by name, so an unknown name raises `SerializerDoesNotExist`. ## Pitfall in API code Returning `serializers.serialize("json", qs)` from a view is a common junior shortcut. It leaks every column (including ones like password hashes when the model has them), exposes internal ids and model labels, and gives the client a format designed for fixtures. For an API, use DRF or build the response explicitly.

  • Why does serializing a Django child model with multi-table inheritance lose data?
    Only fields declared locally on the child are serialized; the parent's fields live in the parent's table and belong to the parent model's rows. To round-trip the data you must serialize both the child queryset and the corresponding parent rows. Abstract base classes do not have this problem, because their fields are local to the concrete model.

saying these in an interview costs you the question

  • django.core.serializers validates data like a form before saving
  • Passing fields=[...] can remove pk from the serialized output
  • Django's serializers and DRF's serializers are the same class hierarchy
  • deserialize() returns saved model instances ready to use
  • Serializing a multi-table child model writes the parent's fields too