Django поиск по значению массива JSONField

Допустим, у меня есть записи базы данных MySQL с такой структурой

{
            "id": 44207,
            "actors": [
                {
                    "id": "9c88bd9c-f41b-59fa-bfb6-427b1755ea64",
                    "name": "APT41",
                    "scope": "confirmed"
                },
                {
                    "id": "6f82bd9c-f31b-59fa-bf26-427b1355ea64",
                    "name": "APT67",
                    "scope": "confirmed"
                }
            ],
},
{
            "id": 44208,
            "actors": [
                {
                    "id": "427b1355ea64-bfb6-59fa-bfb6-427b1755ea64",
                    "name": "APT21",
                    "scope": "confirmed"
                },
                {
                    "id": "9c88bd9c-f31b-59fa-bf26-427b1355ea64",
                    "name": "APT22",
                    "scope": "confirmed"
                }
            ],
},
...

Как я могу отфильтровать все объекты, имя актеров которых содержит '67', например?

Ближайший вариант, который у меня есть - это то, что я заставил его работать так:

queryset.filter(actors__contains=[{"name":"APT67"}])

Но этот запрос соответствует точному значению actor.name, в то время как я хочу принять оператор 'contains'.

У меня это также работает при запросе со строгим индексом массива, например, так:

queryset.filter(actors__0__name__icontains='67')

Но он работает только если первый элемент в массиве соответствует моему запросу. А мне нужно, чтобы объект возвращался в любом из его актеров, соответствующих моему запросу, поэтому я ожидал, что будет работать что-то вроде queryset.filter(actors__name__icontains='67'), но это не работает :(

)

До сих пор мне приходится использовать models.Q и несколько OR для поддержки моих потребностей, вот так -

search_query = models.Q(actors__0__name__icontains='67') | models.Q(actors__1__name__icontains='67') | models.Q(actors__2__name__icontains='67') | models.Q(actors__3__name__icontains='67')
queryset.filter(search_query)

но это выглядит ужасно и поддерживает только 4 элемента поиска (или я должен включить больше OR'ов)

Есть ли подсказки, можно ли это решить нормальным способом в целом?

После этого ответа и связанного ответа в том же посте.

'contains' или 'icontains' ищет шаблоны '%string%', что в вашем случае предполагает, что '67' находится между символами. Но числовой шаблон находится в конце имени вашего актера.

Итак, основываясь на ответах, которые я связал, вы, вероятно, должны попробовать endswith или iendswith, чтобы искать шаблон '%67'

Моя модель данных:

class MyCustomModel(models.Model):
   id = models.BigAutoField(primary_key=True)
   actors = models.JSONField(blank=True, null=True)

В итоге я сделал довольно хакерский оператор lookup, который заменяет '$."' на '$[*]."' в моих запросах к полю JSON, что в моем случае делало правильный запрос, фильтруя все объекты, чье поле JSON с массивом объектов, содержит одно из нужных свойств.

Оператор поиска:

from django.db.models.lookups import IContains
from django.db.models import Field

# Custom lookup which acts like the default IContains lookup but also replaces field name to match all JSON array objects ['*'].field_name in the query instead of $.field_name.
# Maybe this could be done in a better way with better Q field path, but this works for now.
class JSONArrayContains(IContains):
    lookup_name = 'jsonarraycontains'
    
    def __init__(self, lhs, rhs):
        self.lookup_name = 'icontains' # we fake the lookup name to get the right operators further
        super().__init__(lhs, rhs)
        

    def as_sql(self, compiler, connection):
        lhs_sql, params = self.process_lhs(compiler, connection)

        # !! HERE IS THE MAGIC
        # we need to replace params parts which are like '$."name"' into parts like '$[*]."name"' if param is string and matches $." pattern
        params = [param.replace('$."', '$[*]."') if isinstance(param, str) and param.startswith('$."') else param for param in params] 

        rhs_sql, rhs_params = self.process_rhs(compiler, connection)
        params.extend(rhs_params)
        rhs_sql = self.get_rhs_op(connection, rhs_sql)
        return f'{lhs_sql} {rhs_sql}', params

Использование:

queryset.filter(actors__name__jsonarraycontains='67')

Фильтрует все записи, которые

Вернуться на верх