coveralls: 
[![Coverage Status](https://coveralls.io/builds/11035390/badge)](https://coveralls.io/builds/11035390)

Coverage remained the same at 100.0% when pulling **1ff658e188518d178c0fd584f68598ce998c71bf on fix/issue-48-fix-many-fields-deserialization** into **957a1c3164b91c64f4b0e64e1901536aeb2aeecd on master**.

diff --git a/src/graceful/serializers.py b/src/graceful/serializers.py
index 7af5fc3..14a0d4e 100644
--- a/src/graceful/serializers.py
+++ b/src/graceful/serializers.py
@@ -1,7 +1,7 @@
 from collections import OrderedDict
 from collections.abc import Mapping, MutableMapping
 
-from graceful.errors import DeserializationError, ValidationError
+from graceful.errors import DeserializationError
 from graceful.fields import BaseField
 
 
@@ -168,12 +168,25 @@ class BaseSerializer(metaclass=MetaSerializer):
                 continue
 
             try:
-                # if field has explicitly specified source then use it
-                # else fallback to field name.
-                # Note: field does not know its name
-                object_dict[_source(name, field)] = field.from_representation(
-                    representation[name]
-                )
+                if (
+                    # note: we cannot check for any sequence or iterable
+                    #       because of strings and nested dicts.
+                    not isinstance(representation[name], (list, tuple)) and
+                    field.many
+                ):
+                    raise ValueError("field should be sequence")
+
+                source = _source(name, field)
+                value = representation[name]
+
+                if field.many:
+                    object_dict[source] = [
+                        field.from_representation(single_value)
+                        for single_value in value
+                    ]
+                else:
+                    object_dict[source] = field.from_representation(value)
+
             except ValueError as err:
                 failed[name] = str(err)
 
@@ -229,10 +242,17 @@ class BaseSerializer(metaclass=MetaSerializer):
         ]
 
         invalid = {}
-        for name, value in object_dict.items():
+        for index, (name, value) in enumerate(object_dict.items()):
             try:
-                sources[name].validate(value)
-            except ValidationError as err:
+                field = sources[name]
+
+                if field.many:
+                    for single_value in value:
+                        field.validate(single_value)
+                else:
+                    field.validate(value)
+
+            except ValueError as err:
                 invalid[name] = str(err)
 
         if any([missing, forbidden, invalid]):

Improperly handled fields with many=True
Restrict "many" field handling to lists in similar way we handle serialization.
diff --git a/tests/test_serializers.py b/tests/test_serializers.py
index 7ec4804..5e7b353 100644
--- a/tests/test_serializers.py
+++ b/tests/test_serializers.py
@@ -3,7 +3,9 @@
 All tested serializer classes should be defined within tests because
 we test how whole framework for defining new serializers works.
 """
-from graceful.fields import BaseField
+import pytest
+
+from graceful.fields import BaseField, StringField
 from graceful.serializers import BaseSerializer
 
 
@@ -245,15 +247,46 @@ def test_serializer_describe():
     ])
 
 
-def test_serialiser_representation_with_field_many():
+def test_serialiser_with_field_many():
     class UpperField(BaseField):
         def to_representation(self, value):
             return value.upper()
 
+        def from_representation(self, data):
+            return data.upper()
+
     class ExampleSerializer(BaseSerializer):
         up = UpperField(details='multiple values field', many=True)
 
     serializer = ExampleSerializer()
-    instance = {'up': ["aa", "bb", "cc"]}
+    obj = {'up': ["aa", "bb", "cc"]}
+    desired = {'up': ["AA", "BB", "CC"]}
+
+    assert serializer.to_representation(obj) == desired
+    assert serializer.from_representation(obj) == desired
+
+    with pytest.raises(ValueError):
+        serializer.from_representation({"up": "definitely not a sequence"})
+
+
+def test_serializer_many_validation():
+    def is_upper(value):
+        if value.upper() != value:
+            raise ValueError("should be upper")
+
+    class ExampleSerializer(BaseSerializer):
+        up = StringField(
+            details='multiple values field',
+            many=True,
+            validators=[is_upper]
+        )
+
+    invalid = {'up': ["aa", "bb", "cc"]}
+    valid = {'up': ["AA", "BB", "CC"]}
+
+    serializer = ExampleSerializer()
+
+    with pytest.raises(ValueError):
+        serializer.validate(invalid)
 
-    assert serializer.to_representation(instance) == {"up": ["AA", "BB", "CC"]}
+    serializer.validate(valid)

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
3.6
requirements-docs.txt requirements-tests.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.9
attrs==22.2.0
Babel==2.11.0
certifi==2021.5.30
docutils==0.12
falcon==3.1.3
-e git+https://github.com/swistakm/graceful.git@957a1c3164b91c64f4b0e64e1901536aeb2aeecd#egg=graceful
imagesize==1.4.1
importlib-metadata==4.8.3
iniconfig==1.1.1
Jinja2==3.0.3
MarkupSafe==2.0.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
Pygments==2.14.0
pypandoc==1.14
pyparsing==3.1.4
pytest==7.0.1
pytz==2025.2
singledispatch==3.7.0
six==1.17.0
snowballstemmer==2.2.0
Sphinx==1.4.8
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0

name: graceful
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.9
      - attrs==22.2.0
      - babel==2.11.0
      - docutils==0.12
      - falcon==3.1.3
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - jinja2==3.0.3
      - markupsafe==2.0.1
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pygments==2.14.0
      - pypandoc==1.14
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytz==2025.2
      - singledispatch==3.7.0
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==1.4.8
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
prefix: /opt/conda/envs/graceful

tests/test_serializers.py::test_serialiser_with_field_many tests/test_serializers.py::test_serializer_many_validation
tests/test_serializers.py::test_simple_serializer_definition tests/test_serializers.py::test_empty_serializer_definition tests/test_serializers.py::test_serializer_inheritance tests/test_serializers.py::test_serializer_field_overriding tests/test_serializers.py::test_serialiser_simple_representation tests/test_serializers.py::test_serialiser_sources_representation tests/test_serializers.py::test_serializer_set_attribute tests/test_serializers.py::test_serializer_get_attribute tests/test_serializers.py::test_serializer_source_wildcard tests/test_serializers.py::test_serializer_source_field_with_wildcard tests/test_serializers.py::test_serializer_describe
src/graceful/serializers.py
src/graceful/serializers.py