Repository: yukinarit/pyserde

Issue:
NewType affects untagged Union deserialization
A rather complex bug which I struggle to describe. Here's an example:


```python
from typing import Union, Optional, NewType
from serde import deserialize, Untagged, from_dict
from dataclasses import dataclass

OtherNewtypeThing = NewType('OtherNewtypeThing', str)


@deserialize
class SupposedlyUnrelated:
    name: OtherNewtypeThing  # works if you change type to str

@deserialize
class Innerclass:
    inner_value: str = 'SHOULD_NOT_APPEAR'


@deserialize(tagging=Untagged)
class MyDataclass:
    unrelateds: list[SupposedlyUnrelated]
    buggy_field: list[Union[str, Innerclass]]


data = {'unrelateds': [], 'buggy_field':[{'inner_value': 'value'}, 'something']}
actual = from_dict(MyDataclass, data)

print(actual)
```

#### Expected

```
MyDataclass(unrelateds=[], buggy_field=[Innerclass(inner_value='value'), 'something'])
```

#### Actual

```
MyDataclass(unrelateds=[], buggy_field=[Innerclass(inner_value='value'), Innerclass(optional_inner='SHOULD_NOT_APPEAR')])
```

Instead of deserializing `"something"` to `str` which matches `Union[str, Innerclass]`, instead the value is mistakenly interpreted as `Innerclass`. This happens despite that `"something"` is not deserializable to `Innerclass`

#### Affected Version

pyserde 0.9.6 is affected.

This is a regression from pyserde 0.8.3

#### Workaround

Downgrade to pyserde 0.8.3

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/yukinarit/pyserde/pull/313?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yukinarit) Report
Base: **90.91**% // Head: **90.91**% // No change to project coverage :thumbsup:
> Coverage data is based on head [(`ab835ce`)](https://codecov.io/gh/yukinarit/pyserde/pull/313?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yukinarit) compared to base [(`507a73b`)](https://codecov.io/gh/yukinarit/pyserde/commit/507a73b1db6702ac05cd7d523c91c5a36f4b4691?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yukinarit).
> Patch coverage: 100.00% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #313   +/-   ##
=======================================
  Coverage   90.91%   90.91%           
=======================================
  Files          12       12           
  Lines        1717     1717           
  Branches      362      362           
=======================================
  Hits         1561     1561           
  Misses        111      111           
  Partials       45       45           
```


| [Impacted Files](https://codecov.io/gh/yukinarit/pyserde/pull/313?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yukinarit) | Coverage Δ | |
|---|---|---|
| [serde/de.py](https://codecov.io/gh/yukinarit/pyserde/pull/313?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yukinarit#diff-c2VyZGUvZGUucHk=) | `97.47% <100.00%> (ø)` | |
| [serde/se.py](https://codecov.io/gh/yukinarit/pyserde/pull/313?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yukinarit#diff-c2VyZGUvc2UucHk=) | `96.92% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yukinarit). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yukinarit)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/yukinarit/pyserde/pull/313?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yukinarit).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yukinarit).

yukinarit: Hi @jennydaman

Sorry for the delay, but I found how to fix this. PTAL if this PR resolves your issue 🙏 

Patch:
diff --git a/serde/de.py b/serde/de.py
index 0212e65..85fee4d 100644
--- a/serde/de.py
+++ b/serde/de.py
@@ -38,7 +38,6 @@ from .compat import (
     is_generic,
     is_list,
     is_literal,
-    is_new_type_primitive,
     is_none,
     is_opt,
     is_primitive,
@@ -273,7 +272,7 @@ def deserialize(
                 # We call deserialize and not wrap to make sure that we will use the default serde
                 # configuration for generating the deserialization function.
                 deserialize(typ)
-            if typ is cls or (is_primitive(typ) and not is_enum(typ) and not is_new_type_primitive(typ)):
+            if typ is cls or (is_primitive(typ) and not is_enum(typ)):
                 continue
             if is_generic(typ):
                 g[typename(typ)] = get_origin(typ)
@@ -1006,7 +1005,7 @@ def {{func}}(cls=cls, maybe_generic=None, data=None, reuse_instances = {{serde_s
     fake_dict = {"fake_key": data}
     {% endif %}
 
-    {% if t|is_primitive or t |is_none %}
+    {% if t|is_primitive or t|is_none %}
     if not isinstance(fake_dict["fake_key"], {{t|typename}}):
         raise Exception("Not a type of {{t|typename}}")
     {% endif %}
diff --git a/serde/se.py b/serde/se.py
index 347c267..0f41258 100644
--- a/serde/se.py
+++ b/serde/se.py
@@ -32,7 +32,6 @@ from .compat import (
     is_generic,
     is_list,
     is_literal,
-    is_new_type_primitive,
     is_none,
     is_opt,
     is_primitive,
@@ -273,7 +272,7 @@ def serialize(
                 # configuration for generating the serialization function.
                 serialize(typ)
 
-            if typ is cls or (is_primitive(typ) and not is_enum(typ) and not is_new_type_primitive(typ)):
+            if typ is cls or (is_primitive(typ) and not is_enum(typ)):
                 continue
             g[typename(typ)] = typ
 


Test patch:
diff --git a/tests/test_union.py b/tests/test_union.py
index dc08c0c..503883e 100644
--- a/tests/test_union.py
+++ b/tests/test_union.py
@@ -2,7 +2,7 @@ import logging
 import sys
 from dataclasses import dataclass
 from ipaddress import IPv4Address
-from typing import Dict, Generic, List, Optional, Tuple, TypeVar, Union
+from typing import Dict, Generic, List, NewType, Optional, Tuple, TypeVar, Union
 from uuid import UUID
 
 import pytest
@@ -674,3 +674,32 @@ def test_untagged():
     f = Foo({10: 'bar'})
     with pytest.raises(Exception):
         assert to_dict(from_dict(Foo, d)) == f
+
+
+def test_newtype_and_untagged_union() -> None:
+    """
+    Regression test for
+    https://github.com/yukinarit/pyserde/issues/292
+    """
+    from serde import Untagged
+
+    OtherNewtypeThing = NewType('OtherNewtypeThing', str)
+
+    @serde
+    class SupposedlyUnrelated:
+        name: OtherNewtypeThing
+
+    @serde
+    class Innerclass:
+        inner_value: str = 'SHOULD_NOT_APPEAR'
+
+    @serde(tagging=Untagged)
+    class MyDataclass:
+        unrelateds: List[SupposedlyUnrelated]
+        buggy_field: List[Union[str, Innerclass]]
+
+    data = {'unrelateds': [], 'buggy_field': [{'inner_value': 'value'}, 'something']}
+    actual = from_dict(MyDataclass, data)
+
+    assert isinstance(actual.buggy_field[0], Innerclass)
+    assert isinstance(actual.buggy_field[1], str)