Repository: seandstewart/typical (language: python)

Issue:
tojson and primitive fails when class is derived
* typical version: 2.7.4
* Python version: 3.8.11
* Operating System: Linux

### Description
Try to make one class inherit another class. When using `tojson ` or `primitive` on derived class get `RecursionError`. 

### What I Did
```
import typic

@typic.klass
class A:
    a :int

@typic.klass
class B(A):
    b:int

b = B(1,2)

try:
    b.tojson()
except RecursionError as e:
    print(e)
    b.primitive()
    
```
Traceback:
```
/nix/store/6v5r9jyh7c96gh3j4bg8niakhk34dbrk-python3.8-typical-2.7.4/lib/python3.8/site-packages/typic/klass.py:236: UserWarning: Keyword `always` will default to `False` in a future version. You should update your code to either explicitly declare `always=True` or update your code to not assume values will be coerced when set.
  return make_typedclass(
maximum recursion depth exceeded while calling a Python object
Traceback (most recent call last):
  File "test.py", line 14, in <module>
    b.tojson()
  File "/nix/store/6v5r9jyh7c96gh3j4bg8niakhk34dbrk-python3.8-typical-2.7.4/lib/python3.8/site-packages/typic/ext/json.py", line 34, in tojson
    return __dumps(__prim(o), **kwargs)
  File "<typical generated serializer__5288976827520983837>", line 7, in serializer__5288976827520983837
  File "/nix/store/6v5r9jyh7c96gh3j4bg8niakhk34dbrk-python3.8-typical-2.7.4/lib/python3.8/site-packages/typic/serde/resolver.py", line 670, in iterator
    return __fields(o)
  File "/nix/store/6v5r9jyh7c96gh3j4bg8niakhk34dbrk-python3.8-typical-2.7.4/lib/python3.8/site-packages/typic/serde/resolver.py", line 670, in iterator
    return __fields(o)
  File "/nix/store/6v5r9jyh7c96gh3j4bg8niakhk34dbrk-python3.8-typical-2.7.4/lib/python3.8/site-packages/typic/serde/resolver.py", line 670, in iterator
    return __fields(o)
  [Previous line repeated 496 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 17, in <module>
    b.primitive()
  File "<typical generated serializer__5288976827520983837>", line 7, in serializer__5288976827520983837
  File "/nix/store/6v5r9jyh7c96gh3j4bg8niakhk34dbrk-python3.8-typical-2.7.4/lib/python3.8/site-packages/typic/serde/resolver.py", line 670, in iterator
    return __fields(o)
  File "/nix/store/6v5r9jyh7c96gh3j4bg8niakhk34dbrk-python3.8-typical-2.7.4/lib/python3.8/site-packages/typic/serde/resolver.py", line 670, in iterator
    return __fields(o)
  File "/nix/store/6v5r9jyh7c96gh3j4bg8niakhk34dbrk-python3.8-typical-2.7.4/lib/python3.8/site-packages/typic/serde/resolver.py", line 670, in iterator
    return __fields(o)
  [Previous line repeated 496 more times]
RecursionError: maximum recursion depth exceeded
```


Discussion / hints:

Fix patch:
diff --git a/pyproject.toml b/pyproject.toml
index 30d6f7e6..a588a0d8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
 [tool.poetry]
 name = "typical"
 packages = [{include = "typic"}]
-version = "2.7.4"
+version = "2.7.5"
 description = "Typical: Python's Typing Toolkit."
 authors = ["Sean Stewart <sean_stewart@me.com>"]
 license = "MIT"
@@ -157,7 +157,7 @@ requires = ["poetry>=0.12"]
 build-backend = "poetry.masonry.api"
 
 [bumpver]
-current_version = "v2.7.4"
+current_version = "v2.7.5"
 version_pattern = "vMAJOR.MINOR.PATCH[-TAGNUM]"
 commit_message = "bump version to {new_version} [ci skip]"
 tag = false
diff --git a/typic/__init__.py b/typic/__init__.py
index b4e0de44..0fdb1e8e 100644
--- a/typic/__init__.py
+++ b/typic/__init__.py
@@ -13,4 +13,4 @@
 al = typed
 
 
-__version__ = "2.7.4"
+__version__ = "2.7.5"
diff --git a/typic/checks.py b/typic/checks.py
index 273b7958..40e0b671 100644
--- a/typic/checks.py
+++ b/typic/checks.py
@@ -28,6 +28,7 @@
     Iterator,
     Hashable,
     NamedTuple,
+    TYPE_CHECKING,
 )
 
 import typic
@@ -46,6 +47,9 @@
     Record,
 )
 
+if TYPE_CHECKING:
+    from typic.api import TypicObjectT
+
 ObjectT = TypeVar("ObjectT")
 """A type-alias for a python object.
 
@@ -841,3 +845,7 @@ def isabstract(o) -> TypeGuard[abc.ABC]:
 
 # Custom list of ABCs which incorrectly evaluate to false
 _ABCS = frozenset({numbers.Number})
+
+
+def istypicklass(obj) -> TypeGuard[TypicObjectT]:
+    return hasattr(obj, "__typic_fields__")
diff --git a/typic/serde/ser.py b/typic/serde/ser.py
index fe316031..3744028a 100644
--- a/typic/serde/ser.py
+++ b/typic/serde/ser.py
@@ -365,12 +365,14 @@ def _compile_serializer(self, annotation: Annotation[Type[_T]]) -> SerializerT[_
                     # Mapping types need special nested processing as well
                     istypeddict = checks.istypeddict(origin)
                     istypedtuple = checks.istypedtuple(origin)
+                    istypicklass = checks.istypicklass(origin)
                     if not istypeddict and issubclass(origin, self._DICTITER):
                         self._build_dict_serializer(func, annotation)
                     # Array types need nested processing.
                     elif (
                         not istypedtuple
                         and not istypeddict
+                        and not istypicklass
                         and issubclass(origin, self._LISTITER)
                     ):
                         self._build_list_serializer(func, annotation)
diff --git a/typic/serde/translator.py b/typic/serde/translator.py
index 8554c386..66fa3f14 100644
--- a/typic/serde/translator.py
+++ b/typic/serde/translator.py
@@ -24,6 +24,7 @@
     isnamedtuple,
     isiteratortype,
     isbuiltinsubtype,
+    istypicklass,
 )
 from typic.compat import lru_cache
 from typic.gen import Block, Keyword, ParameterKind
@@ -153,16 +154,17 @@ def iterator(
         exclude: Tuple[str, ...] = (),
     ) -> IteratorT:
         """Get an iterator function for a given type, if possible."""
-        mapping, iterable, builtin, namedtuple = (
+        mapping, iterable, builtin, namedtuple, typicklass = (
             ismappingtype(type),
             isiterabletype(type),
             isbuiltinsubtype(type),
             isnamedtuple(type),
+            istypicklass(type),
         )
         if mapping:
             return _valuescaller if values else _itemscaller
 
-        if (iterable, namedtuple) == (True, False):
+        if (iterable, namedtuple, typicklass) == (True, False, False):
             return iter if values else enumerate
 
         if (builtin, iterable) == (True, False):


Test patch:
diff --git a/tests/test_serdes.py b/tests/test_serdes.py
index c648d59d..431079a3 100644
--- a/tests/test_serdes.py
+++ b/tests/test_serdes.py
@@ -185,6 +185,7 @@ class MemberInt:
             ListUnion([MemberStr("string"), MemberInt(1)]),
             {"members": [{"field": "string"}, {"field": 1}]},
         ),
+        (objects.SubTypic, objects.SubTypic(var="", sub=""), {"var": "", "sub": ""}),
     ],
 )
 def test_serde_serialize(t, obj, prim):
@@ -261,6 +262,7 @@ def test_serde_serialize(t, obj, prim):
             "1",
         ),
         (typing.Union[float, int, str], "foo", "foo"),
+        (objects.SubTypic, objects.SubTypic(var="", sub=""), {"var": "", "sub": ""}),
     ],
 )
 def test_serde_deserialize(t, obj, prim):