Skip to content

Commit 5d40079

Browse files
committed
Add nested serialiser example
1 parent b06c8f2 commit 5d40079

File tree

1 file changed

+64
-2
lines changed

1 file changed

+64
-2
lines changed

docs/api-guide/fields.md

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -685,8 +685,9 @@ the coordinate pair:
685685
fields = ['label', 'coordinates']
686686

687687
Note that this example doesn't handle validation. Partly for that reason, in a
688-
real project, the coordinate nesting might be better handled with a nested serialiser using two
689-
`IntegerField` instances, each with `source='*'`.
688+
real project, the coordinate nesting might be better handled with a nested serialiser
689+
using `source='*'`, with two `IntegerField` instances, each with their own `source`
690+
pointing to the relevant field.
690691

691692
The key points from the example, though, are:
692693

@@ -717,6 +718,67 @@ suitable for updating our target object. With `source='*'`, the return from
717718
('y_coordinate', 4),
718719
('x_coordinate', 3)])
719720

721+
For completeness lets do the same thing again but with the nested serialiser
722+
approach suggested above:
723+
724+
class NestedCoordinateSerializer(serializers.Serializer):
725+
x = serializers.IntegerField(source='x_coordinate')
726+
y = serializers.IntegerField(source='y_coordinate')
727+
728+
729+
class DataPointSerializer(serializers.ModelSerializer):
730+
coordinates = NestedCoordinateSerializer(source='*')
731+
732+
class Meta:
733+
model = DataPoint
734+
fields = ['label', 'coordinates']
735+
736+
Here the mapping between the target and source attribute pairs (`x` and
737+
`x_coordinate`, `y` and `y_coordinate`) is handled in the `IntegerField`
738+
declarations. It's our `NestedCoordinateSerializer` that takes `source='*'`.
739+
740+
Our new `DataPointSerializer` exhibits the same behaviour as the custom field
741+
approach.
742+
743+
Serialising:
744+
745+
>>> out_serializer = DataPointSerializer(instance)
746+
>>> out_serializer.data
747+
ReturnDict([('label', 'testing'),
748+
('coordinates', OrderedDict([('x', 1), ('y', 2)]))])
749+
750+
Deserialising:
751+
752+
>>> in_serializer = DataPointSerializer(data=data)
753+
>>> in_serializer.is_valid()
754+
True
755+
>>> in_serializer.validated_data
756+
OrderedDict([('label', 'still testing'),
757+
('x_coordinate', 3),
758+
('y_coordinate', 4)])
759+
760+
But we also get the built-in validation for free:
761+
762+
>>> invalid_data = {
763+
... "label": "still testing",
764+
... "coordinates": {
765+
... "x": 'a',
766+
... "y": 'b',
767+
... }
768+
... }
769+
>>> invalid_serializer = DataPointSerializer(data=invalid_data)
770+
>>> invalid_serializer.is_valid()
771+
False
772+
>>> invalid_serializer.errors
773+
ReturnDict([('coordinates',
774+
{'x': ['A valid integer is required.'],
775+
'y': ['A valid integer is required.']})])
776+
777+
For this reason, the nested serialiser approach would be the first to try. You
778+
would use the custom field approach when the nested serialiser becomes infeasible
779+
or overly complex.
780+
781+
720782
# Third party packages
721783

722784
The following third party packages are also available.

0 commit comments

Comments
 (0)