diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 89c0a714c..d5d828c39 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1469,17 +1469,17 @@ class MultipleChoiceField(ChoiceField): if not self.allow_empty and len(data) == 0: self.fail('empty') - return { - # Arguments for super() are needed because of scoping inside - # comprehensions. + # Arguments for super() are needed because of scoping inside + # comprehensions. + return list(dict.fromkeys([ super(MultipleChoiceField, self).to_internal_value(item) for item in data - } + ])) def to_representation(self, value): - return { + return list(dict.fromkeys([ self.choice_strings_to_values.get(str(item), item) for item in value - } + ])) class FilePathField(ChoiceField): diff --git a/tests/test_fields.py b/tests/test_fields.py index d574b07eb..0b6f4945c 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -11,6 +11,8 @@ from zoneinfo import ZoneInfo import pytest +from rest_framework.utils import json + try: import pytz except ImportError: @@ -2056,16 +2058,18 @@ class TestMultipleChoiceField(FieldValues): Valid and invalid values for `MultipleChoiceField`. """ valid_inputs = { - (): set(), - ('aircon',): {'aircon'}, - ('aircon', 'manual'): {'aircon', 'manual'}, + (): list(), + ('aircon',): ['aircon'], + ('aircon', 'manual'): ['aircon', 'manual'], + ('manual', 'aircon'): ['manual', 'aircon'], } invalid_inputs = { 'abc': ['Expected a list of items but got type "str".'], ('aircon', 'incorrect'): ['"incorrect" is not a valid choice.'] } outputs = [ - (['aircon', 'manual', 'incorrect'], {'aircon', 'manual', 'incorrect'}) + (['aircon', 'manual', 'incorrect'], ['aircon', 'manual', 'incorrect']), + (['manual', 'aircon', 'incorrect'], ['manual', 'aircon', 'incorrect']), ] field = serializers.MultipleChoiceField( choices=[ @@ -2082,6 +2086,27 @@ class TestMultipleChoiceField(FieldValues): field.partial = True assert field.get_value(QueryDict('')) == rest_framework.fields.empty + def test_valid_inputs_is_json_serializable(self): + for input_value, _ in get_items(self.valid_inputs): + validated = self.field.run_validation(input_value) + + try: + json.dumps(validated) + except TypeError as e: + pytest.fail(f'Validated output not JSON serializable: {repr(validated)}; Error: {e}') + + def test_output_is_json_serializable(self): + for output_value, _ in get_items(self.outputs): + representation = self.field.to_representation(output_value) + + try: + json.dumps(representation) + except TypeError as e: + pytest.fail( + f'to_representation output not JSON serializable: ' + f'{repr(representation)}; Error: {e}' + ) + class TestEmptyMultipleChoiceField(FieldValues): """ diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py index b7195494c..4a71dcbcc 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -199,14 +199,14 @@ class TestNestedSerializerWithList: serializer = self.Serializer(data=input_data) assert serializer.is_valid() - assert serializer.validated_data['nested']['example'] == {1, 2} + assert serializer.validated_data['nested']['example'] == [1, 2] def test_nested_serializer_with_list_multipart(self): input_data = QueryDict('nested.example=1&nested.example=2') serializer = self.Serializer(data=input_data) assert serializer.is_valid() - assert serializer.validated_data['nested']['example'] == {1, 2} + assert serializer.validated_data['nested']['example'] == [1, 2] class TestNotRequiredNestedSerializerWithMany: