Filtering an array for specific types with GROQ

In Sanity, you can have arrays that contain different objects. But what if we want to filter out the specific types inside the array when we run a query? A problem I came across was how to map the different Sanity objects to .NET objects based on their type.

Disclaimer: I'm by far no GROQ-master (yet!) so this might have been an easy task for some of you.

So, I have an array called Contentarea in Sanity. Here, the editors can insert different objects like persons, custom components and files. When querying the array I need to sort the types so I can deserialize them properly. I tried a bunch of different things, and ended up with something like this:

*[_type == 'article' && slug.current == $currentSlug][0] {
    ...,
	'mfnComponent': *[_type == 'mfnComponent' && _id in ^.contentarea[]._ref] {
        'mfnComponentType': mfnComponentType
	}
}

Although this works, I thought this looks a bit messy. And running an unnecessary query for all components inside the array when they are already there seems redundant.

Later I needed to query and filter out all the file objects, and I found that this didn't work the same way, since it's not a created document type as the other types I queried. The closest I got was using select functions in GROQ.

*[_type == 'article' && slug.current == $currentSlug][0] {
    ...,
	'files': contentarea[] {
		_type == 'file' => {
			'file': asset-> {
                title,
             	url,
             	originalFilename
			}
	}      
}

This kind of worked. I got all my data for the files, but this also returned an empty object for each other object inside the array.

The solution  

I sent a question to the Sanity community slack and got an answer from Racheal from Sanity. It is possible to run the filter inside the array brackets! So now my query looks like this:

*[_type == 'article' && slug.current == $currentSlug][0] {
    ...,
	'files': contentarea[@._type == 'file'] {
  		'': asset-> {
    		title,
    		url,
    		originalFilename
  		} 		
	},
	'persons': contentarea[@->._type == 'person']-> {
         worktitle,
         name,
         phone,
         email
     }
}

And I get only the data I need, and can now easily deserialize the objects into my .NET classes.  🥳


Bonus tip!

As seen in the code snippet above, I'm assigning asset->... to an empty name. This flattens the object.

// Without assigning to empty name, the object looks something like this: 
files: [
    {
        asset: {
            title,
            url,
            originalFilename
        }
    }
]

// By assigning an empty name, we can flatten the object.
files: [
    {
       title,
       url,
       originalFilename
    }
]