Eugene Lisitsky Eugene Lisitsky - 1 year ago 273
JSON Question

Django Rest Framework: Serialize data from nested json fields to plain object

I want to serialize non-flat structure to a one flat object.
Here's an example of an API call I receive (I cannot control it unfortunately):

"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

I'd like to serialize it to the models like these:

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()

As you can see, model
's field
is located on the same object as
unlike in JSON data.

My Serializer are next:

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',

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)
print('to_internal_value', ret)
return ret

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

I works well for fields in
object in JSON.
But how can I add to the JiraIssueSerializer some fields like
? They are not direct fields of
object, but located in substrcucture
I see these ways:

  • Make one more Model
    to keep them, but it's ridiculous. I do not need it and my API strictly depends on the foreign structure.

  • Make some
    convertors. But in this case I have to manually validate and prepare all the fields in my
    and repeat myself several times. Also If field
    is not enlisted in Serializer, latter cannot use it or validation fails.

  • Add
    to Serializer (commented in code) and take data from it. Is it good? How to validate it? Again, I have n clean structure of code.

Next I'd like to parse and save changelog data.

How to deal better with such structures?

Thank you!

Answer Source

Finally, solution was found in tests of django-rest-framework.

You may easily define nested serializers which will act as a containers and extract data to your plain object. Like so:

    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(
     assert serializer.is_valid()

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