JSONField(default=dict) and using .get() results in an (unexpected) error.
Bug report
What's wrong
Seems that using JSONField(default=dict) and using .get() results in an (unexpected) error.
error: Item "None" of "Optional[Any]" has no attribute "get" [union-attr]
class SomeModel(models.Model):
data = models.JSONField(default=dict)
some_models = SomeModel.objects.all().values("data")
for i in some_models:
something = i["data"].get("key")
Results in
error: Item "None" of "Optional[Any]" has no attribute "get" [union-attr]
How is that should be
Since the default in the JSONField is set to dict I would expect django-stubs to recognize the get() method.
System information
- OS:
-
pythonversion: 3.8.13 -
djangoversion: 3.2.16 -
mypyversion: 0.961 -
django-stubsversion: 1.12.0 -
django-stubs-extversion: 0.5.0
A bit more context. We've checked the inferred field type with just a model instance, with only, and with values, and only when using values the type is inferred as Optional[Any] instead of just Any.
a = False
if a:
reveal_type(SomeModel.objects.get().data) # Revealed type is "Any"
reveal_type(SomeModel.objects.values('data').get()['data']) # Revealed type is "Union[Any, None]"
reveal_type(SomeModel.objects.only('data').get().data) # Revealed type is "Any"
This bugged me too. Also, I expect JSONField should have a more specific type than Any.
Unfortunately I don’t think this type can be more specific than Any, for two reasons.
First, because JSONField with a default of dict could still be used to store a list, number, string, or None. Django places no restriction here, the default type is just what will be set if you don’t specify a value.
Second, because using a custom JSON decoder does allow literally any type to be returned.
only when using
valuesthe type is inferred asOptional[Any]instead of justAny.
This is odd and could be investigated, I’m not sure why using values would add an Optional. It is probably not JSONField specific...
Unfortunately I don’t think this type can be more specific than Any, for two reasons.
It would be great to be able to manually refine the type, though:
data: JSONField[dict[str, Any]] = models.JSONField(default=dict)
I tried to do that a few times, no luck. But that's another story and should be a separate issue. This issue is about the type being refined to Optional despite the field being non-nullable.
Then it should be something like this:
data = models.JSONField[dict[str, Any]](default=dict)
no?
Have you tried:
if TYPE_CHECKING:
JSONField = models.JSONField[dict[str, Any]]
else:
JSONField = models.JSONField
and then
json_property = JSONField(...)