GROQ query to find nested custom blocks with invalid marks array

5 replies
Last updated: Apr 3, 2022
We have managed to import some bad data in some documents, in a nested custom component, and I am trying to write a groq query to find what documents contain bad data.The problem is that some articles have ended up with a mark on a custom block, which ends up giving an error when we try to render it, because the marks aren’t a part of the schema. an example of such a bad block:

{
  "_key": "afd98068bfc1",
  "_type": "bilde",
  "altText": "",
  "caption": "",
  "image": {
    "_type": "image",
    "asset": {
      "_ref": "image-b3d77a3ad3970d959d3bf83d6d2c02ad320cbb71-1000x681-jpg",
      "_type": "reference"
    }
  },
  "marks": [
    {
      "_key": "3c540f80b489",
      "_type": "link",
      "href": "<https://some-url>"
    }
  ]
},
this lives in a nested type, so the structure is something like this

brodtekst[_type == 'hjelpeartikkel.expandCollapse'].innhold[_type == 'bilde']
and I am trying to find all documents containing
bilde
with the bad
marks
array, but I can’t seem to find the right filtering to useI have tried something like this:

*[_type == 'hjelpeartikkel'
  && brodtekst[_type == 'hjelpeartikkel.expandCollapse'].innhold[_type == 'bilde'].marks[] != null]{
  _id,
    brodtekst[_type == 'hjelpeartikkel.expandCollapse'] {
      innhold[_type == 'bilde'] {
        ...
      }
    }
}
but that ends up returning documents where there are no
marks
, I’ve also tried to
count(brodtekst[_type == 'hjelpeartikkel.expandCollapse'].innhold[_type == 'bilde'].marks[]) > 0
and similar things, but they all end up either matching 0 documents, or too many documents. any pointers on how I can achieve this filter, or is it easier to just fetch all and manually go through them in javascript?
AI Update

I can see you're struggling with filtering deeply nested arrays in GROQ. This is a common challenge, and the key is understanding how GROQ handles array traversal in filter conditions versus projections.

The issue you're encountering is that when you write something like:

brodtekst[_type == 'hjelpeartikkel.expandCollapse'].innhold[_type == 'bilde'].marks[]

This creates a flattened array of all marks from all matching bilde objects across all matching brodtekst items. When you then check != null, you're not checking whether specific documents have marks, but rather whether this flattened array contains anything.

Here's a working approach using nested count() functions to check for the existence of marks at the right nesting level:

*[_type == 'hjelpeartikkel' && 
  count(brodtekst[
    _type == 'hjelpeartikkel.expandCollapse' && 
    count(innhold[_type == 'bilde' && defined(marks)]) > 0
  ]) > 0
]{
  _id,
  "problematicBlocks": brodtekst[_type == 'hjelpeartikkel.expandCollapse']{
    innhold[_type == 'bilde' && defined(marks)]{
      _key,
      _type,
      marks
    }
  }
}

This query works by:

  1. Using count() at each nesting level to properly evaluate whether matching items exist
  2. The inner filter innhold[_type == 'bilde' && defined(marks)] only matches bilde objects that have a marks field
  3. The outer count(...) > 0 checks if there's at least one matching expandCollapse item
  4. In the projection, the same filter returns only the problematic blocks

If you want to be more specific and only find bilde objects where marks is an array with actual content (not just defined but empty), you can use:

*[_type == 'hjelpeartikkel' && 
  count(brodtekst[
    _type == 'hjelpeartikkel.expandCollapse' && 
    count(innhold[_type == 'bilde' && count(marks) > 0]) > 0
  ]) > 0
]

The count(marks) > 0 ensures you're only matching bilde objects where the marks array actually contains elements.

Why your original attempts didn't work:

  • brodtekst[].innhold[].marks[] != null flattens all marks across all documents into a single array, then compares that array to null (which will always be true if any marks exist anywhere)
  • While chained array syntax like array[].nestedArray[] works for projections, when you're using it in filter conditions with comparisons, the flattening behavior can produce unexpected results
  • Using count() at each nesting level gives you explicit control over what you're checking at each depth

If you're still getting unexpected results, you might also try using length(marks) > 0 as an alternative to count(marks) > 0, though they should behave similarly for arrays. Another option is to add && length(marks) > 0 for extra certainty that it's a non-empty array.

Show original thread
5 replies
Hi User. I’m curious if this gets what you’re after:

*[_type == 'hjelpeartikkel'
  && brodtekst[_type == 'hjelpeartikkel.expandCollapse'].innhold[_type == 'bilde' && marks[] != null]]
hmm, no, can’t seem to get that to match any documents (and I know there is still at least one such document)
but I think
*[_type == 'hjelpeartikkel' && count(brodtekst[_type == 'hjelpeartikkel.expandCollapse'].innhold[_type == 'bilde' && marks[] != null]) > 0]{
  _id
}
might have done it
yeah it did, thanks for the help
user A
!
Great! Glad you got it worked out! 🙌

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?