|
| 1 | +# pyright: reportMissingImports=false |
1 | 2 | import os |
2 | 3 | import re |
3 | 4 | from collections.abc import Callable |
@@ -31,16 +32,27 @@ def load_runtime() -> None: |
31 | 32 | load_runtime() |
32 | 33 |
|
33 | 34 | import clr # noqa: E402 |
34 | | -from System import ( # noqa: E402 # pyright: ignore[reportMissingImports] |
| 35 | + |
| 36 | +# .NET types are imported late because we need to load the .NET runtime first |
| 37 | + |
| 38 | +clr.AddReference("System.Text.Json") |
| 39 | +clr.AddReference("System.Text.Encodings.Web") |
| 40 | + |
| 41 | +from System import ( # noqa: E402 |
35 | 42 | Activator, |
36 | 43 | ArgumentException, |
37 | 44 | Boolean, |
| 45 | + Double, |
38 | 46 | Enum, |
39 | 47 | Int32, |
| 48 | + Object, |
40 | 49 | String, |
41 | 50 | Type, |
42 | 51 | ) |
43 | | -from System.Reflection import BindingFlags # pyright: ignore[reportMissingImports] # noqa: E402 |
| 52 | +from System.Collections import ArrayList # noqa: E402 |
| 53 | +from System.Collections.Generic import Dictionary # noqa: E402 |
| 54 | +from System.Reflection import BindingFlags # noqa: E402 |
| 55 | +from System.Text.Json import JsonSerializerOptions # noqa: E402 |
44 | 56 |
|
45 | 57 |
|
46 | 58 | def get_object_types() -> dict[str, "System.RuntimeType"]: |
@@ -156,6 +168,7 @@ def values_fn(_cls: type) -> list[int]: |
156 | 168 | FormatterType = types["Formatter"] |
157 | 169 | FracturedJsonOptionsType = types["FracturedJsonOptions"] |
158 | 170 |
|
| 171 | + |
159 | 172 | __all__ = [ |
160 | 173 | "Formatter", |
161 | 174 | "FracturedJsonOptions", |
@@ -292,6 +305,18 @@ def __init__(self, options: FracturedJsonOptions | None = None) -> None: |
292 | 305 | options_property = FormatterType.GetProperty("Options") |
293 | 306 | options_property.SetValue(self._dotnet_instance, options._dotnet_instance) # noqa: SLF001 |
294 | 307 |
|
| 308 | + # Python.NET is unable to find a matching method when we call Formatter.Serialize |
| 309 | + # directly so we need to coerce it |
| 310 | + self._serialize_method = None |
| 311 | + methods = self._dotnet_instance.GetType().GetMethods( |
| 312 | + BindingFlags.Public | BindingFlags.Instance, |
| 313 | + ) |
| 314 | + serialize_methods = [m for m in methods if m.Name == "Serialize"] |
| 315 | + for m in serialize_methods: |
| 316 | + params = m.GetParameters() |
| 317 | + if len(params) == 3 and params[2].Name == "serOpts": # noqa: PLR2004 |
| 318 | + self._serialize_method = m |
| 319 | + |
295 | 320 | @property |
296 | 321 | def options(self) -> FracturedJsonOptions: |
297 | 322 | """Gets/sets the formatting options (FracturedJsonOptions).""" |
@@ -342,3 +367,48 @@ def dotnet_wrapper(s_dotnet: String) -> Int32: |
342 | 367 | return Int32(result) |
343 | 368 |
|
344 | 369 | self._dotnet_instance.StringLengthFunc = Func[String, Int32](dotnet_wrapper) |
| 370 | + |
| 371 | + @staticmethod |
| 372 | + def to_dotnet(obj: object) -> object: # noqa: PLR0911 |
| 373 | + """Recursively converts a JSON-serializable Python object to a .NET object.""" |
| 374 | + if isinstance(obj, dict): |
| 375 | + dotnet_dict = Dictionary[String, Object]() |
| 376 | + for k, v in obj.items(): |
| 377 | + # Keys must be strings |
| 378 | + key = str(k) |
| 379 | + dotnet_dict[key] = Formatter.to_dotnet(v) |
| 380 | + return dotnet_dict |
| 381 | + |
| 382 | + if isinstance(obj, (list, tuple)): |
| 383 | + dotnet_list = ArrayList() |
| 384 | + for item in obj: |
| 385 | + dotnet_list.Add(Formatter.to_dotnet(item)) |
| 386 | + return dotnet_list |
| 387 | + |
| 388 | + if isinstance(obj, bool): |
| 389 | + return Boolean(obj) |
| 390 | + if isinstance(obj, int): |
| 391 | + return Int32(obj) |
| 392 | + if isinstance(obj, float): |
| 393 | + return Double(obj) |
| 394 | + if isinstance(obj, str): |
| 395 | + return String(obj) |
| 396 | + if obj is None: |
| 397 | + return None |
| 398 | + |
| 399 | + msg = f"Type '{type(obj).__name__}' not supported for serialization" |
| 400 | + raise TypeError(msg) |
| 401 | + |
| 402 | + def serialize( |
| 403 | + self, |
| 404 | + obj: object, |
| 405 | + starting_depth: int = 0, |
| 406 | + ser_opts: JsonSerializerOptions = None, |
| 407 | + ) -> str: |
| 408 | + """Format a JSON-serializable Python object into a string.""" |
| 409 | + dotnet_obj = Formatter.to_dotnet(obj) |
| 410 | + generic_method = self._serialize_method.MakeGenericMethod(type(dotnet_obj)) |
| 411 | + return generic_method.Invoke( |
| 412 | + self._dotnet_instance, |
| 413 | + [dotnet_obj, Int32(starting_depth), ser_opts], |
| 414 | + ) |
0 commit comments