diff --git a/DSPythonNet3/Encoders/ListEncodeDecoder.cs b/DSPythonNet3/Encoders/ListEncodeDecoder.cs index 91b8c18..eec28d6 100644 --- a/DSPythonNet3/Encoders/ListEncodeDecoder.cs +++ b/DSPythonNet3/Encoders/ListEncodeDecoder.cs @@ -28,18 +28,39 @@ public bool TryDecode(PyObject pyObj, out T value) return false; } - using (var pyList = PyList.AsList(pyObj)) + if (typeof(T).IsGenericType) { - if (typeof(T).IsGenericType) + var genericDef = typeof(T).GetGenericTypeDefinition(); + var elementType = typeof(T).GetGenericArguments().FirstOrDefault(); + + if (elementType != null && (genericDef == typeof(IEnumerable<>) || genericDef == typeof(List<>) || genericDef == typeof(IList<>))) { - value = pyList.ToList(); + using var pyList = PyList.AsList(pyObj); + var listType = typeof(List<>).MakeGenericType(elementType); + var typedList = (IList)Activator.CreateInstance(listType)!; + + foreach (PyObject item in pyList) + { + using (item) + { + typedList.Add(ConvertGenericItem(item, elementType)); + } + } + + value = (T)typedList; + return true; } - else + + using (var pyList = PyList.AsList(pyObj)) { - value = (T)pyList.ToList(); + value = pyList.ToList(); } return true; } + + var converted = ConvertToArrayList(pyObj); + value = (T)converted; + return true; } public PyObject TryEncode(object value) @@ -57,5 +78,105 @@ bool IPyObjectDecoder.CanDecode(PyType objectType, Type targetType) } return decodableTypes.IndexOf(targetType) >= 0; } + + private static IList ConvertToArrayList(PyObject pyObj) + { + using var pyList = PyList.AsList(pyObj); + var result = new ArrayList(); + foreach (PyObject item in pyList) + { + using (item) + { + result.Add(ConvertItem(item)); + } + } + return result; + } + + private static object ConvertItem(PyObject item) + { + if (TryGetClrObject(item, out var clrObject)) + { + return clrObject; + } + + if (PyInt.IsIntType(item)) + { + using var pyLong = PyInt.AsInt(item); + try { return pyLong.ToInt64(); } + catch (PythonException ex) when (ex.Message.StartsWith("int too big")) + { + return pyLong.ToBigInteger(); + } + } + + if (PyFloat.IsFloatType(item)) + { + using var pyFloat = PyFloat.AsFloat(item); + return pyFloat.ToDouble(); + } + + if (PyString.IsStringType(item)) + { + return item.AsManagedObject(typeof(string)); + } + + if (PyList.IsListType(item) || PyTuple.IsTupleType(item)) + { + return ConvertToArrayList(item); + } + + return item.AsManagedObject(typeof(object)); + } + + private static bool TryGetClrObject(PyObject pyObj, out object clrObject) + { + try + { + clrObject = pyObj.GetManagedObject(); + if (clrObject is PyObject || clrObject is null) + { + clrObject = null; + return false; + } + return true; + } + catch + { + clrObject = null; + return false; + } + } + + private static object ConvertGenericItem(PyObject item, Type elementType) + { + if (elementType == typeof(object)) + { + return ConvertItem(item); + } + + if (TryGetClrObject(item, out var clrObject) && elementType.IsInstanceOfType(clrObject)) + { + return clrObject; + } + + if (PyList.IsListType(item) || PyTuple.IsTupleType(item)) + { + // recursively decode nested generics + var listType = typeof(List<>).MakeGenericType(elementType); + var nestedList = (IList)Activator.CreateInstance(listType)!; + using var pyList = PyList.AsList(item); + foreach (PyObject child in pyList) + { + using (child) + { + nestedList.Add(ConvertGenericItem(child, elementType)); + } + } + return nestedList; + } + + return item.AsManagedObject(elementType); + } } } diff --git a/pipeline.yml b/pipeline.yml index b4193bb..7b179c5 100644 --- a/pipeline.yml +++ b/pipeline.yml @@ -1,4 +1,4 @@ -version: 1.4.5 +version: 1.4.6 pipeline_os: windows create_pr_release_to_master: true