glideapps / glideapps/quicktype

Correct support for optional class properties in Python

Open
#1,391 0 comments 0 reactions 0 assignees View on GitHub
Python
Dominant language
TypeScript
Stars
13.9k
Forks
1.2k
Avg merge
8h 53m
Merged PRs (30d)
369

Description

Defining a class property as optional (i.e. by not including it in the "required" list) in a JSON schema does not work for Python currently, since Python does not distinguish between `null` and `undefined` as JavaScript/TypeScript: for Python these are both represented as `None` values. This leads to a problem when serializing from Python to JSON to TypeScript, since the Python side would have added the class property in the JSON serialization with a `null` value, while the TypeScript parser expects either a value or undefined, and thus raises an error because of the `null` value.

The behavior defined above stems from the `optionalToNullable` conversion done when encountering a `PythonRenderer` (because `supportsOptionalClassProperties` is `false`).

However, in my opinion it looks much nicer to define a class property as optional instead of as a union between the type and `null` (i.e. `prop?: string` looks better than `prop: string | null`).

The following could be a solution for adding proper support for optional class properties to Python:
1. Set `supportsOptionalClassProperties` to `true`
2. Modify `PythonRenderer.pythonType` to also accept the `isOptional` flag from the class property (or just the ClassProperty as its only argument and derive the type from that), and wrap the produces type descriptor in `Optional[]` if `isOptional` is `true`
3. Modify `JSONPythonRenderer.emitClassMembers` to include some extra code in the `from_dict` and `to_dict` functions for removing optional fields from the generated dict for optional properties if their value is `None`:
```python
class MyObject:
opt_prop: Optional[str]

def __init__(self, opt_prop: Optional[str]) -> None:
self.opt_prop = opt_prop

def from_dict(obj: Any) -> 'MyObject':
assert isinstance(obj, dict)
opt_prop = obj.get("optProp")
if opt_prop is not None:
opt_prop = from_str(opt_prop)
return MyObject(opt_prop)

def to_dict(self) -> dict:
result: dict = {}
if self.opt_prop is not None:
result["optProp"] = from_str(self.opt_prop)
return result
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.