Filter 'if array contains'

Hello,

I have an issue filtering records.

What I try to achieve:
Every record can have multiple user types (be shown for different user types).
I want to filter out records, which don’t have a current user’s type (don’t show a record for a user if it’s not supposed to be shown for this type of a user)

What I did:
I added a new JSON ‘userTypes’ field to my schema. So now I have a nicely defined array of values that a content maker can use to specify user types. It results in an array of user types in the gql response.

Problem:
JSON field supports only the exists filtering. But I’d like to have something like this:

allItems (filter: { userTypes: { anyIn: [${user.type}] } }) {

I haven’t found any other easy solution for this problem, whilst it looks quite common.

AC:

  • values of userTypes are strictly specified, so ‘content maker’ can select only from the predefined set of values
  • userTypes is an array of values (not a string)
  • we can filter records if userTypes doesn’t contain a specified value in gql

Please let me know if any additional details would be helpful.

Thanks in advance,
Yevhen Tataryn

Hi @yevhen,

Welcome to the forum, and thank you for the question!

Just to be sure I understand what you’re asking for, could you please explain your use case here a bit better? Who are the “users” — are they editors (collaborators) in your DatoCMS project, or unrelated users that you’re syncing in from some external source?

If you’re trying to limit record visibility by DatoCMS user, I think a cleaner & safer way to approach this would be to use our roles & permissions system and create a separate model for each set of users. They can have the same or similar schema, but you can then limit each model to only be viewable by certain user types/roles.

If this filtering has nothing to do with your DatoCMS collaborators and userTypes is just an arbitrary field, we unfortunately don’t offer serverside deep-filtering of JSON fields right now. A few workarounds:

  • Instead of an array of strings, you can make a separate model for “userTypes” and use a multi-link field instead. This would overall be safer anyway since you can link to specific userType record by internal UUID rather than a hardcoded user-facing string. You do get back an array of values (their UUIDs), but you can pretty easily look up their other values (like the userType label) if you need to.
  • You can filter userTypes on the clientside (i.e., download all records and then filter them locally in JS)
  • You can use the computed fields plugin or a webhook or an API call to mirror the JSON array into a single-line string value, and then do a regex pattern filter on that single-line string… but that’s a hack.

Personally I think the first option (making a separate “userTypes” model with its own records) would be the cleanest. Would that work for you?

Hello @roger ,

Thank you for the quick reply.

Yes, ‘userType’ is just an arbitrary value. You can think of it as a ‘tag system’. I have many records, every record has one or more tags. User may provide a list of tags, and I need to filter records which contain at least one tag from the provided list.

The ‘model’ approach sounds good, but I can’t make it work.

  1. In the ‘Schema → Models’ section I created a separate model ‘audience’, which has a ‘single-line string’ property. This property has a ‘Select input’ with 4 values (“USER_TYPE_A”, “USER_TYPE_B”, “USER_TYPE_C”, “USER_TYPE_D”).
  2. Based on the model I have created 4 records (for each value).
  3. I added a ‘Multiple Link’ property - reference to this model.
  4. Now I try to query and filter, but here I face a problem.
    My query looks like this:
query {
  allItems {
      _firstPublishedAt
      title
      audienceref {
        __typename
        id
        audience
      }
  }
}

Here I’m interested in the ‘audienceref’ field. In the response it looks like:

"audienceref": [
          {
            "__typename": "AudienceRecord",
            "id": "OPdXq-OXRHK_ZlAjGs8DWQ",
            "audience": "USER_TYPE_A"
          },
          {
            "__typename": "AudienceRecord",
            "id": "XI-3sWytQuePEuVIOV4IKQ",
            "audience": "USER_TYPE_B"
          }
        ],

If I do

alltems (filter: {audienceref : {anyIn : ["USER_TYPE_A"]}}) {
  • it throws an error ‘cannot coerce \"USER_TYPE_A\" to ItemId’.

I see that I can filter by Id, like :

alltems (filter: {audienceref : {anyIn : ["OP12dXq-77RHK_56AjGs8D12"]}}) {` 

But I need to filter by actual value (userType), not an Id.

So how to filter it right?

Thanks in advance,
Yevhen Tataryn

Hey @yevhen,

Sorry for not being clearer about this. If you don’t want to filter by ID but by the value of a string in the audience model, you have to use our Inverse Relationships feature instead. You’d look up “all audience records with the field value of ____, and give me the related items”.

You probably also don’t need the audience field to be dropdown; each one can just be a unique string.

I sent you an invite to an example project so you can better see how it could work.

To explain…

The Audience model consists of a 4 separate records, each with a single-line string field:

In the model settings for audience, you’d enable “Inverse Relationships”:

The Item records link to one or more individual Audience records:

Then you’d filter it like this:

query MyQuery {
  # note that you're NOT filtering on `allItems` but on `allAudiences`... THEN getting related items
  filteredByInverseRelationship: allAudiences(filter: {name: {in: ["USER_TYPE_B", "USER_TYPE_C"]}}) {
    _allReferencingItems {
      ...ItemFragment
    }
  }
}

fragment ItemFragment on ItemRecord {
  title
  audienceref {
    name
    id
  }
}

Which will return:

{
  "data": {
    "filteredByInverseRelationship": [
      {
        "_allReferencingItems": [
          {
            "title": "Example item 3",
            "audienceref": [
              {
                "name": "USER_TYPE_C",
                "id": "d0xzO1E6TB2OBKzWb6W-1Q"
              }
            ]
          }
        ]
      },
      {
        "_allReferencingItems": [
          {
            "title": "Example item 1",
            "audienceref": [
              {
                "name": "USER_TYPE_A",
                "id": "Hzv5cIMWRTSmMtBGYhhv1A"
              },
              {
                "name": "USER_TYPE_B",
                "id": "YksIorBkQ9e-VwFUjoCoiw"
              }
            ]
          }
        ]
      }
    ]
  }
}

Does that help?