Django Rest Framework: сериализация данных из вложенных json-полей в простой объект

Я хочу сериализовать неровную структуру на один плоский объект. Вот пример вызова API, который я получаю (к сожалению, я не могу его контролировать):

{
"webhookEvent": "jira:issue_updated",
"user": {
        "id": 2434,
        "name": "Ben",
    },
"issue": {
      "id": "33062",
      "key": "jira-project-key-111",
      "fields": {
          "summary": "The week ahead",
      },
"changelog": {
    "id": "219580",
    "items": [{
         "field": "status",
         "fieldtype": "jira",
         "from": "10127",
         "fromString": "Submitted",
         "to": "10128",
         "toString": "Staged"
    }]
},
"timestamp": 1423234723378
}

Я хотел бы сериализовать его для таких моделей, как:

class Issue(models.Model):
    jira_id = models.IntegerField()
    jira_id = models.CharField()
    summary = models.CharField()

class Change(models.Model):
    issue = models.ForeignKey(Issue)
    timestamp = models.DataTimeField()

Как вы можете видеть, поле Issue summary находится на том же объекте, что и id и key, в отличие от данных JSON.

Мой Сериализатор следующий:

    class ChangeSerializer(serializers.ModelSerializer):
        """Receives complex data from jira and converts into objects."""

        issue = JiraIssueSerializer()
        timestamp = TimestampField(source='created_at')

        class Meta:
            model = Change
            fields = ('issue', 'timestamp')

        def create(self, validated_data):
            super(serializers.ModelSerializer, self).create(validated_data=validated_data)
            jira_issue = JiraIssueSerializer(data=validated_data)
            issue = Issue.objects.get(jira_issue)
            self.created_at = datetime.utcnow()
            change = Change(**validated_data)
            return change



    class JiraIssueSerializer(serializers.ModelSerializer):
        """Issue serializer."""
        id = serializers.IntegerField(source='jira_id')
        key = serializers.CharField(source='jira_key')
        summary = serializers.CharField()   ### I want field to work!
        # fields = serializers.DictField(child=serializers.CharField())

        class Meta:
            model = Issue
            fields = ('id', 'key',
               'summary',
            )

        def to_internal_value(self, data):
           # ret = super(serializers.ModelSerializer,   self).to_internal_value(data)
           ret = {}
           # ret = super().to_internal_value(data)
           ret['jira_id'] = data.get('id', None)
           ret['jira_key'] = data.get('key', None)
           jira_issue_fields_data = data.get('fields')
           if jira_issue_fields_data or 1:
               summary = jira_issue_fields_data.get('summary', None)
               ret.update(summary=summary)
            print('to_internal_value', ret)
            return ret

         def to_representation(self, instance):
            ret = {}
            ret = super().to_representation(instance)
            fields = {}
            fields['summary'] = instance.summary
            ret.update(fields=fields)
            print(ret)
            return ret

Я хорошо работаю для полей в объекте Issue в JSON. Но как я могу добавить в JiraIssueSerializer некоторые поля, например summary? Они не являются прямыми полями объекта Issue, но расположены в substrcucture fields. Я вижу следующие способы:

  • Сделайте еще одну модель fields, чтобы сохранить их, но это смешно. Мне это не нужно, и мой API строго зависит от внешней структуры.

  • Сделайте несколько преобразователей .to_internal_fields(). Но в этом случае я должен вручную проверить и подготовить все поля в моем Issue и повторить несколько раз. Также если поле summary не зачислено в Сериализатор, последний не может его использовать или проверка не выполняется.

  • Добавить DictField в Serializer (прокомментированный в коде) и взять данные из него. Это хорошо? Как проверить его? Опять же, у меня есть чистая структура кода.

Далее я хотел бы проанализировать и сохранить данные изменений.

Как лучше справляться с такими структурами?

Спасибо!

Ответ 1

Наконец, решение было найдено в тестах django-rest-framework.
https://github.com/tomchristie/django-rest-framework/blob/master/tests/test_serializer.py#L149

Вы можете легко определить вложенные сериализаторы, которые будут действовать как контейнеры и извлекать данные в ваш простой объект. Например:

    class NestedSerializer1(serializers.Serializer):
        a = serializers.IntegerField()
        b = serializers.IntegerField()

    class NestedSerializer2(serializers.Serializer):
        c = serializers.IntegerField()
        d = serializers.IntegerField()

    class TestSerializer(serializers.Serializer):
        nested1 = NestedSerializer1(source='*')
        nested2 = NestedSerializer2(source='*')

    data = {
        'nested1': {'a': 1, 'b': 2},
        'nested2': {'c': 3, 'd': 4}
     }

     serializer = TestSerializer(data=self.data)
     assert serializer.is_valid()

     assert serializer.validated_data == {
        'a': 1, 
        'b': 2,
        'c': 3, 
        'd': 4
    }

Ответ 2

Я бы предложил вам создать собственный пользовательский сериализатор для получения данных. Вы можете сделать это так:

from rest_framework import serializers

class MySerializer(serializers.Serializer):
    """
    Custom serializer
    """
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField()


    def create(self, validated_data):
        """Create a new object"""
        validated_data['custom_value'] = 0 # you can manipulate and restructure data here if you wish
        return MyModel.objects.create(**validated_data)

Затем вы можете манипулировать данными, как вы пожелаете, в функции create(). Вы также можете создавать вложенные пользовательские сериализаторы для анализа этих данных.

Ответ 3

Документация играет важную роль в вложении в сериализацию.

По сути, вы хотите создать отдельный класс с вложенными значениями следующим образом:

class UserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    username = serializers.CharField(max_length=100)

class CommentSerializer(serializers.Serializer):
    user = UserSerializer()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()