Repository: adamboche/python-marshmallow-union

Issue:
Support union for mapping value
When using a union for a mapping value-type, marshmallow-union raises `ExceptionGroup` during serialisation.

All candidate fields raise as marshmallow-union calls `schema.serialise(attr, obj, ...)`. However, inside a mapping value-type, marshmallow calls `schema._serialise(value, None, None)`, as the mapping values are not part of the serialised object.

### Reproduction
```python
import typing as t
import dataclasses
import marshmallow
import marshmallow_dataclass

@marshmallow_dataclass.dataclass
class Foo:
    bar: t.Dict[str, t.Union[int, str]] = dataclasses.field(default_factory=dict)

obj = Foo(bar={"spam": "eggs", "ham": 42})
schema = Foo.Schema()
schema.dump(obj)
```

### Expected
```python
{'bar': {'spam': 'eggs', 'ham': 42}}
```

### Actual
```python
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "~/env-tmp/lib/python3.7/site-packages/marshmallow/schema.py", line 553, in dump
    result = self._serialize(processed_obj, many=many)
  File "~/env-tmp/lib/python3.7/site-packages/marshmallow/schema.py", line 517, in _serialize
    value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute)
  File "~/env-tmp/lib/python3.7/site-packages/marshmallow/fields.py", line 325, in serialize
    return self._serialize(value, attr, obj, **kwargs)
  File "`/env-tmp/lib/python3.7/site-packages/marshmallow/fields.py", line 1510, in _serialize
    result[keys[k]] = self.value_field._serialize(v, None, None, **kwargs)
  File "~/env-tmp/lib/python3.7/site-packages/marshmallow_union/__init__.py", line 69, in _serialize
    raise ExceptionGroup("All serializers raised exceptions.\n", error_store.errors)
marshmallow_union.ExceptionGroup: ('All serializers raised exceptions.\n', {})
```

### Environment
* OS: Ubuntu 19:10
* Python: 3.7.5
* marshmallow: 3.2.2
* marshmallow-union: 0.1.12
* marshmallow-dataclass: 7.1

Discussion:

Patch:
diff --git a/src/marshmallow_union/__init__.py b/src/marshmallow_union/__init__.py
index 69e3099..b3b86e1 100644
--- a/src/marshmallow_union/__init__.py
+++ b/src/marshmallow_union/__init__.py
@@ -59,9 +59,22 @@ class Union(marshmallow.fields.Field):
         for candidate_field in fields:
 
             try:
-                return candidate_field.serialize(
-                    attr, obj, error_store=error_store, **kwargs
-                )
+                try:
+                    return candidate_field.serialize(
+                        attr, obj, error_store=error_store, **kwargs
+                    )
+                except TypeError:
+                    # When serialising a mapping (eg dict) value item, 'attr' and 'obj'
+                    # is none (as a dict value is not an attribute of anything). This
+                    # causes issues with the attribute-get methods within
+                    # 'marshmallow', but can be bypassed by passing the known 'value'
+                    # directly to '_serialize'
+                    if attr is obj is None:
+                        # pylint: disable=protected-access
+                        return candidate_field._serialize(
+                            value, attr, obj, **kwargs
+                        )
+                    raise
             # pylint: disable=broad-except
             except Exception as exc:
                 pass


Test patch:
diff --git a/tests/test_union.py b/tests/test_union.py
index 10e6066..c4ffd4f 100644
--- a/tests/test_union.py
+++ b/tests/test_union.py
@@ -32,6 +32,19 @@ class OtherSchema(marshmallow.Schema):
     )
 
 
+class MappingSchema(marshmallow.Schema):
+    """Schema with union inside mapping."""
+    items = marshmallow.fields.Dict(
+        marshmallow.fields.String(),
+        marshmallow_union.Union(
+            [
+                marshmallow.fields.Integer(),
+                marshmallow.fields.List(marshmallow.fields.Integer()),
+            ],
+        ),
+    )
+
+
 class StrIntSchema(marshmallow.Schema):
     """Schema with str and int candidates."""
 
@@ -46,6 +59,7 @@ class StrIntSchema(marshmallow.Schema):
         ({"name": "Alice", "number_or_numbers": [25, 50]}, OtherSchema()),
         ({"x": 5}, StrIntSchema()),
         ({"x": "hello"}, StrIntSchema()),
+        ({"items": {"a": 42, "b": [17]}}, MappingSchema()),
     ],
 )
 def test_round_trip(data, schema):
@@ -60,6 +74,7 @@ def test_round_trip(data, schema):
     [
         ({"name": "Alice", "number_or_numbers": "twenty-five"}, PersonSchema()),
         ({"name": "Alice", "number_or_numbers": {"x": 14}}, PersonSchema()),
+        ({"items": {"a": 42, "b": "spam"}}, MappingSchema()),
     ],
 )
 def test_raises(data, schema):