From 9b66f7d7b6fcf3ec004d0269f97709b6e3f85a74 Mon Sep 17 00:00:00 2001
From: Nandan Rao <nandanmarkrao@gmail.com>
Date: Fri, 26 Feb 2021 12:39:54 +0100
Subject: [PATCH] Added support for Any and Dict types.

rebased against 0.10.4
---
 tests/test_decoding.py  | 25 +++++++++++++++++++++++++
 typedjson/annotation.py |  5 ++---
 typedjson/decoding.py   | 40 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 67 insertions(+), 3 deletions(-)

diff --git a/tests/test_decoding.py b/tests/test_decoding.py
index 71abb5d..9825932 100644
--- a/tests/test_decoding.py
+++ b/tests/test_decoding.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python3
 
 from typing import Any
+from typing import Dict
 from typing import Generic
 from typing import List
 from typing import Optional
@@ -118,6 +119,30 @@ def test_can_decode_heterogeneous_list() -> None:
     assert typedjson.decode(List[Union[str, int]], json) == json
 
 
+def test_can_decode_any_list() -> None:
+    json = [1, "foo"]
+    assert typedjson.decode(List[Any], json) == json
+
+
+def test_can_decode_string_any_dict() -> None:
+    json = {"foo": 1, "bar": "baz"}
+    assert typedjson.decode(Dict[str, Any], json) == json
+
+
+def test_cannot_decode_dict_with_bad_key() -> None:
+    json = {"foo": 1, 5: "baz"}
+
+    expectation = DecodingError(TypeMismatch(("5",)))
+    assert typedjson.decode(Dict[str, Any], json) == expectation
+
+
+def test_cannot_decode_dict_with_bad_value() -> None:
+    json = {"foo": 1, "baz": "baz"}
+
+    expectation = DecodingError(TypeMismatch(("foo",)))
+    assert typedjson.decode(Dict[str, str], json) == expectation
+
+
 def test_can_decode_dataclass() -> None:
     json = {"id": "test-user", "age": 28, "name": {"first": "Tomoya", "last": "Kose"}}
 
diff --git a/typedjson/annotation.py b/typedjson/annotation.py
index 818efe9..fdd717e 100644
--- a/typedjson/annotation.py
+++ b/typedjson/annotation.py
@@ -20,9 +20,8 @@ def hints_of(type_: Type) -> Optional[Dict[str, Type]]:
     type__ = type_ if origin is None else origin
     mapping = dict(zip(parameters_of(type_), args))
 
-    # if hasattr(type__, '__annotations__'):
-    if hasattr(type__, "__init__"):
-        annotations = get_type_hints(type__.__init__)
+    if hasattr(type__, "__annotations__"):
+        annotations = get_type_hints(type__)
         if len(mapping) > 0:
             annotations_: Dict[str, Type] = {}
             for n, t in annotations.items():
diff --git a/typedjson/decoding.py b/typedjson/decoding.py
index 5467386..9c92d7f 100644
--- a/typedjson/decoding.py
+++ b/typedjson/decoding.py
@@ -2,6 +2,7 @@
 
 from typing import Any
 from typing import Iterable
+from typing import Dict
 from typing import Iterator
 from typing import List
 from typing import Optional
@@ -10,6 +11,7 @@
 from typing import TypeVar
 from typing import Union
 
+
 Decoded = TypeVar("Decoded")
 Value = TypeVar("Value")
 
@@ -82,6 +84,8 @@ def decode(
         decode_as_list,
         decode_as_primitive,
         decode_as_class,
+        decode_as_dict,
+        decode_as_any,
     )
 
     result_final: Union[Decoded, DecodingError] = DecodingError(
@@ -123,6 +127,42 @@ def decode_as_primitive(
         return DecodingError(UnsupportedDecoding(path))
 
 
+def decode_as_dict(
+    type_: Type[Decoded], json: Any, path: Path
+) -> Union[Decoded, DecodingError]:
+    from typedjson.annotation import args_of
+    from typedjson.annotation import origin_of
+
+    if isinstance(json, dict) and origin_of(type_) is dict:
+        KeyElement, ValueElement = args_of(type_)
+        dict_decoded: Dict[Any, Any] = {}
+
+        for key, element in json.items():
+            decoded_value = decode(ValueElement, element, path + (str(key),))
+            if isinstance(decoded_value, DecodingError):
+                return decoded_value
+
+            decoded_key = decode(KeyElement, key, path + (str(key),))
+            if isinstance(decoded_key, DecodingError):
+                return decoded_key
+
+            dict_decoded[decoded_key] = decoded_value
+
+        return dict_decoded  # type: ignore
+    else:
+        return DecodingError(UnsupportedDecoding(path))
+
+
+def decode_as_any(
+    type_: Type[Decoded], json: Any, path: Path
+) -> Union[Decoded, DecodingError]:
+
+    if type_ == Any:
+        return json  # type: ignore
+    else:
+        return DecodingError(UnsupportedDecoding(path))
+
+
 def decode_as_class(
     type_: Type[Decoded], json: Any, path: Path
 ) -> Union[Decoded, DecodingError]: