diff --git a/pycardano/plutus.py b/pycardano/plutus.py index 55fe3faf..685af3f3 100644 --- a/pycardano/plutus.py +++ b/pycardano/plutus.py @@ -529,16 +529,14 @@ def _dfs(obj): return {"int": obj} elif isinstance(obj, bytes): return {"bytes": obj.hex()} - elif isinstance(obj, list): - return [_dfs(item) for item in obj] - elif isinstance(obj, IndefiniteList): + elif isinstance(obj, IndefiniteList) or isinstance(obj, list): return {"list": [_dfs(item) for item in obj]} elif isinstance(obj, dict): return {"map": [{"v": _dfs(v), "k": _dfs(k)} for k, v in obj.items()]} elif isinstance(obj, PlutusData): return { "constructor": obj.CONSTR_ID, - "fields": _dfs([getattr(obj, f.name) for f in fields(obj)]), + "fields": [_dfs(getattr(obj, f.name)) for f in fields(obj)], } else: raise TypeError(f"Unexpected type {type(obj)}") @@ -589,6 +587,25 @@ def _dfs(obj): raise DeserializeException( f"Unexpected data structure: {f}." ) + elif ( + hasattr(f_info.type, "__origin__") + and f_info.type.__origin__ is list + ): + t_args = f_info.type.__args__ + if len(t_args) != 1: + raise DeserializeException( + f"List types need exactly one type argument, but got {t_args}" + ) + if "list" not in f: + raise DeserializeException( + f'Expected type "list" for constructor List but got {f}' + ) + t = t_args[0] + if inspect.isclass(t) and issubclass(t, PlutusData): + converted_fields.append(t.from_dict(f)) + else: + converted_fields.append(_dfs(f)) + elif ( hasattr(f_info.type, "__origin__") and f_info.type.__origin__ is dict diff --git a/pycardano/serialization.py b/pycardano/serialization.py index 4e226fa3..ae934fbe 100644 --- a/pycardano/serialization.py +++ b/pycardano/serialization.py @@ -417,6 +417,19 @@ def _restore_dataclass_field( return f.metadata["object_hook"](v) elif isclass(f.type) and issubclass(f.type, CBORSerializable): return f.type.from_primitive(v) + elif hasattr(f.type, "__origin__") and (f.type.__origin__ is list): + t_args = f.type.__args__ + if len(t_args) != 1: + raise DeserializeException( + f"List types need exactly one type argument, but got {t_args}" + ) + t = t_args[0] + if not isinstance(v, list): + raise DeserializeException(f"Expected type list but got {type(v)}") + if isclass(t) and issubclass(t, CBORSerializable): + return IndefiniteList([t.from_primitive(w) for w in v]) + else: + return IndefiniteList(v) elif isclass(f.type) and issubclass(f.type, IndefiniteList): return IndefiniteList(v) elif hasattr(f.type, "__origin__") and (f.type.__origin__ is dict): diff --git a/test/pycardano/test_plutus.py b/test/pycardano/test_plutus.py index 600f45c5..b2f7b3fb 100644 --- a/test/pycardano/test_plutus.py +++ b/test/pycardano/test_plutus.py @@ -2,7 +2,7 @@ import unittest from test.pycardano.util import check_two_way_cbor -from typing import Union, Dict +from typing import Union, Dict, List import pytest @@ -48,6 +48,11 @@ class DictTest(PlutusData): a: Dict[int, LargestTest] +@dataclass +class ListTest(PlutusData): + a: List[LargestTest] + + @dataclass class VestingParam(PlutusData): CONSTR_ID = 1 @@ -104,6 +109,28 @@ def test_plutus_data_json(): assert my_vesting == VestingParam.from_json(encoded_json) +def test_plutus_data_json_list(): + test = ListTest([LargestTest(), LargestTest()]) + encoded_json = test.to_json(separators=(",", ":")) + + assert ( + '{"constructor":0,"fields":[{"list":[{"constructor":9,"fields":[]},{"constructor":9,"fields":[]}]}]}' + == encoded_json + ) + + assert test == ListTest.from_json(encoded_json) + + +def test_plutus_data_cbor_list(): + test = ListTest([LargestTest(), LargestTest()]) + + encoded_cbor = test.to_cbor() + + assert "d8799f82d9050280d9050280ff" == encoded_cbor + + assert test == ListTest.from_cbor(encoded_cbor) + + def test_plutus_data_json_dict(): test = DictTest({0: LargestTest(), 1: LargestTest()})