Opened 8 years ago

Closed 6 days ago

Last modified 6 days ago

#29504 closed Cleanup/optimization (fixed)

Document limitations of using int()able keys with JSONField

Reported by: Shaheed Haque Owned by: Abheet
Component: Documentation Version: dev
Severity: Normal Keywords:
Cc: Carlton Gibson, Clifford Gama Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Shaheed Haque)

The documentation on performing queries inside JSONField values ​https://docs.djangoproject.com/en/2.0/ref/contrib/postgres/fields/#key-index-and-path-lookups says:

If the key is an integer, it will be interpreted as an index lookup in an array:

>>> Dog.objects.filter(data__owner__other_pets__0__name='Fishy')

Note the specific mention of array. While this might be true, it is not the whole truth as applied to dict/object. For example, given a dict/object whose keys are strings (as always in JSON) but which look like integers:

  "employee": {
    "415": {
      "email": "Sherlock.Holmes@acme.co.uk",
      "mobile": "0700 1234567",

how is one supposed to select the "415" bit? It turns out that the same syntax as for the array case applies:

Foo.objects.filter(snapshot__employee__415__mobile='0700 1234567')

This was not at all obvious to me at least, especially as if the "415" is looked up as the final key in the query, the syntax becomes very different:

Foo.objects.filter(snapshot__employee__has_key='415')

Since I wasted quite a bit of time on this, I thought it might be useful to strengthen the documentation in this area to clarify how to lookup:

  • In arrays and dict/objects
    • if the key is the final term in the query
    • if the key is not the final term in the query

Change History (14)

comment:1 by Shaheed Haque, 8 years ago

Description: modified (diff)

comment:2 by Carlton Gibson, 8 years ago

Triage Stage: Unreviewed → Accepted
UI/UX: unset
Version: 2.0 → master

OK, I'm going to provisionally accept this. If you can put together a patch we can have a look and see if there's a clarification to be made.

I'm half-minded to say wontfix since JSON keys must always be strings, and whilst, like '415', they might look like integers, there's no real ambiguity.
But lets see what improvement you have in mind. :)

comment:3 by Carlton Gibson, 8 years ago

Cc: Carlton Gibson added

comment:4 by Shaheed Haque, 8 years ago

I've created ​PR 10077. Please consider.

Last edited 8 years ago by Shaheed Haque (previous) (diff)

comment:5 by Shaheed Haque, 8 years ago

Has patch: set

comment:6 by Tim Graham, 8 years ago

Resolution: → wontfix
Status: new → closed
Type: Uncategorized → Cleanup/optimization

Closing per discussion on PR.

comment:7 by Jacob Walls, 8 days ago

Cc: Clifford Gama added
Has patch: unset
Resolution: wontfix
Status: closed → new
Summary: JSONField dictionary/object lookup using an "integer" key → Document limitations of using int()able keys with JSONField
Triage Stage: Accepted → Unreviewed

I think there's enough ambiguity and lack of support across databases to be worth some documentation here. (Thanks Clifford for pointing me at this from #37297.)

On Postgres, string keys that are int()able can't be queried in the usual way unless more lookups are chained afterward, because when len(key_transforms) == 1, we assume any int()able key is an int.

Then, if you do chain more lookups afterward, it works perfectly fine, but only on Postgres, where the #> ARRAY[... syntax is flexible enough to match either keys or indices. Other databases require specific SQL to match either a key or an index.

from django.db import models

class Person(models.Model):
    metadata = models.JSONField()

def run():
    metadata = {
        "0": "foo",
        "zero": "foo",
    }
    instance = Person.objects.create(metadata=metadata)
    # assert Person.objects.filter(metadata__0="foo").exists()  # fails
    assert Person.objects.filter(metadata__zero="foo").exists()  # passes
    
    metadata = {
        "0": {
            "nested": "foo"
        }
    }
        
    instance = Person.objects.create(metadata=metadata)
    assert Person.objects.filter(metadata__0__nested="foo").exists()  # works on PG only

Given all that, I think we should clarify that int()able keys aren't always queryable?

comment:8 by Abheet, 8 days ago

I'd like to work on this ticket. I'll submit a documentation patch clarifying that int()-able keys in JSONField are not always queryable.

comment:9 by Clifford Gama, 7 days ago

Triage Stage: Unreviewed → Accepted

Thanks Jacob for reopening this!

comment:10 by Abheet, 7 days ago

Has patch: set
Owner: changed from nobody to Abheet
Status: new → assigned

comment:11 by Jacob Walls, 6 days ago

Triage Stage: Accepted → Ready for checkin

comment:12 by Jacob Walls <jacobtylerwalls@…>, 6 days ago

Resolution: → fixed
Status: assigned → closed

In badbcf2:

Fixed #29504 -- Documented limitations of integer-like keys in JSONField.

comment:13 by Jacob Walls <jacobtylerwalls@…>, 6 days ago

In e9f88e3:

Refs #29504 -- Used "object" when referring to JSON keys in query docs.

comment:14 by Jacob Walls <jacobtylerwalls@…>, 6 days ago

In 7e8a8ea:

[6.1.x] Fixed #29504 -- Documented limitations of integer-like keys in JSONField.

Backport of badbcf20e1a2894bf557d9cf0707367da4abfe41 from main.

Note: See TracTickets for help on using tickets.
Back to Top