---
title: "Serializers"
url: https://develop.sentry.dev/backend/api/serializers/
---

# Serializers

Serializers are used to take complex python models and translate them into json. Serializers can also be used to deserialize json back to the python models after validating the incoming data.

At Sentry we have **two** different types of serializers: Django Rest Framework serializers and model serializers.

## [Django Rest Framework](https://develop.sentry.dev/backend/api/serializers.md#django-rest-framework)

Django Rest Framework's serializers are used to handle input validation and transformation for data coming into Sentry.

### [Example](https://develop.sentry.dev/backend/api/serializers.md#example)

In the typical serializer, the fields are specified so that they validate the type and format of the data to your specifications. Django Rest Framework serializers can also save the information into the database if written to fit to the model.

```python
from rest_framework import serializers
from sentry.api.serializers.rest_framework import ValidationError

class ExampleSerializer(serializers.Serializer):
    name = serializers.CharField()
    age = serializers.IntegerField(required=False)
    type = serializers.CharField()

    def validate_type(self, attrs, source):
        type = attrs[source]
        if type not in ['bear', 'rabbit', 'puppy']:
            raise ValidationError('%s is not a valid type' % type)
	return attrs
```

**Field Checking**

In the above example the serializer will accept and validate json containing three fields: `name`, `age`, and `type`. Where `name` and `type` must be `strings` and `age` must be an `integer` as suggested. By default, fields are required, and if not supplied will be marked as invalid by the serializer. Note that the integer field age, required is set to `False`. And so may not be included and the serializer would still be considered valid.

**Custom Validation**

For values that need custom validation (in addition to simple type checking), a

`def validate_<variable_name>(self, attrs, source)`

can be created where `<variable_name>` is substituted with the **exact** variable name as the field is given. So for example if I had a field name `typeName` the validate method name would be `validate_typeName` whereas if I had a field named `type_name` the validate method name would be `validate_type_name`. In the example given above, type is checked an must be a certain string. If a field does not match what your validate method is expecting raise a `ValidationError`.

### [Usage](https://develop.sentry.dev/backend/api/serializers.md#usage)

In an endpoint, this is the typical use of a Django Rest Framework Serializer

```python
class ExampleEndpoint(Endpoint):
    def post(self, request):
        serializer = ExampleSerializer(request.DATA)
        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.object

        #Assuming Example is a model with the same fields
        try:
            with transaction.atomic():
                Example.objects.create(
                    name=result['name'],
                    age=result.get('age'),
                    type=result['type'],
                )
        except IntegrityError:
            return Response('This example already exists', status=409)

        return Response(serialize(result, request.user), status=201)
```

**Validating Data**

The Serializer from the Django Rest Framework will be used in methods with incoming data (i.e. `put` and `post` methods) that need to be validated. Once the serializer is instantiated, you can call `serializer.is_valid()` to validate the data. `serializer.errors` will give feedback on specifically what was invalid about the data given.

For example given input

```python
{
	'age':5,
	'type':'puppy'
}
```

The serializer would return an error stating that the required field name was not provided.

**Saving Data**

Once you have verified that the data is valid, you can save the data in one of two ways. The example given above is the most commonly done in sentry. Taking the `serializer.object` which is simply the validated data (and will be `None` if `serializer.is_valid()` return `False`) and saving that data directly in the model with `<ModelName>.objects.create`.

An alternative method uses more of Django Rest Framework's features, the [ModelSerializer](https://www.django-rest-framework.org/api-guide/serializers/#modelserializer)

```python
from rest_framework import serializers
from sentry.api.serializers.rest_framework import ValidationError

class ExampleSerializer(serializer.ModelSerializer):
    name = serializers.CharField()
    age = serializers.IntegerField(required=False)
    type = serializers.CharField()

    class Meta:
        model = Example

    def validate_type(self, attrs, source):
        type = attrs[source]
        if type not in ['bear', 'rabbit', 'puppy']:
            raise ValidationError('%s is not a valid type' % type)
        return attrs

class ExampleEndpoint(Endpoint):
    def post(self, request):
        serializer = ExampleSerializer(request.DATA)
        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        example = serializer.save()
        return Response(serialize(example, request.user), status=201)
```

## [Model Serializer](https://develop.sentry.dev/backend/api/serializers.md#model-serializer)

[Sentry's Model Serializers](https://github.com/getsentry/sentry/blob/master/src/sentry/api/serializers/base.py) are a home grown version that is used only for outgoing data. The typical model serializer looks like this:

```python
@register(Example)
class ExampleSerializer(Serializer):
    def get_attrs(self, item_list, user):
        attrs = {}
        types = ExampleTypes.objects.filter(
            type_name__in=[i.type for i in item_list]
        )

        for item in item_list:
            attrs[item] = {}
            attrs[item]['type'] = [t for t in types if t.name == item.type_name]
	    return attrs

    def serialize(self, obj, attrs, user):
        return {
            'name':obj.name,
            'type':attrs['type'],
            'age': obj.age,
        }
```

**Registering Model Serializers**

The decorator `@register` is required so that

```python
return Response(serialize(example, request.user), status=201)
```

works. Under the hood it searches for a matching model `Example` in this case, given the type of model the variable `example` is. To match the model serializer with the Model you simply do

```python
@register(<ModelName>)
class ModelSerializer(Serializer):
...
```

**Using get\_attrs to avoid N+1 queries**

For API calls that involve serializing multiple objects (for example, several Organization instances), the top-level [`serialize` function](https://github.com/getsentry/sentry/blob/f5bb22601361802007d628a0b3652256c812c7b5/src/sentry/api/serializers/base.py#L30-L80) is designed to optimize database access and avoid N+1 query problems. The process works in two steps:

1. **Batch calls in `get_attrs`**: The function calls the serializer's `get_attrs` method **once**, passing in the entire list of objects that need to be serialized. This is where you should perform any bulk queries. For example, if you need additional related data (like each object's owner), perform a single query here to fetch all the owners for the list of objects, rather than querying per object. Collect everything you need in advance and construct a mapping (dictionary) of attributes, like so: `attrs[item] = {'attribute_name': attribute}`.

2. **Serialize each object**: Then, the `serialize` method is called once per object in the original list, each time receiving the current object and its corresponding attributes as prepared by `get_attrs`. This ensures no extra or repeated queries are made inside `serialize`—all extra data should already be available via the `attrs` mapping.

```python
# Top-level serialize function (from base.py)
def serialize(objects, user=None, serializer=None, **kwargs):
    # Step 1: get_attrs called ONCE with entire list
    attrs = serializer.get_attrs(
        item_list=[o for o in objects if o is not None],
        user=user,
        **kwargs,
    )
    
    # Step 2: serialize called ONCE PER OBJECT
    return [serializer(o, attrs=attrs.get(o, {}), user=user, **kwargs) for o in objects]
```

This design is why Sentry uses its own serializer pattern instead of something like Django Rest Framework: the explicit use of `get_attrs` allows you to batch-fetch all related data up front, and pass it along efficiently to each per-object serialization call.

**Serialize Method**

Finally you return a dictionary with json serializable information that will be returned with the response.
