django-stubs icon indicating copy to clipboard operation
django-stubs copied to clipboard

JSONField(default=dict) and using .get() results in an (unexpected) error.

Open NiekvanderLinden opened this issue 3 years ago • 6 comments

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:
  • python version: 3.8.13
  • django version: 3.2.16
  • mypy version: 0.961
  • django-stubs version: 1.12.0
  • django-stubs-ext version: 0.5.0

NiekvanderLinden avatar Oct 20 '22 07:10 NiekvanderLinden

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"

orsinium avatar Oct 20 '22 07:10 orsinium

This bugged me too. Also, I expect JSONField should have a more specific type than Any.

PIG208 avatar Nov 08 '22 03:11 PIG208

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 values the type is inferred as Optional[Any] instead of just Any.

This is odd and could be investigated, I’m not sure why using values would add an Optional. It is probably not JSONField specific...

adamchainz avatar Nov 08 '22 07:11 adamchainz

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.

orsinium avatar Nov 08 '22 08:11 orsinium

Then it should be something like this:

data = models.JSONField[dict[str, Any]](default=dict)

no?

sshishov avatar Jan 30 '23 13:01 sshishov

Have you tried:

if TYPE_CHECKING:
    JSONField = models.JSONField[dict[str, Any]]
else:
    JSONField = models.JSONField

and then
json_property = JSONField(...)

drutkoowski avatar Jul 02 '25 07:07 drutkoowski