How to flatten an array of arrays of objects using Groq in Sanity.io.

7 replies
Last updated: Feb 9, 2022
Hello! Is there any way to flatten an array of arrays of objects?I have:

backgrounds: [
  [{ asset: {...} }],
  [{ asset: {...} }]
]
Which I want to, with Groq, turn into:

thumbnails: [
  { asset: {...} },
  { asset: {...} }
],
So far I've tried things like below, to no avail:

'thumbnails': ...backgrounds[]->{
  backgrounds[]{asset}
}
// Unexpected prefix operator "dotDotDot", which makes sense

'thumbnails': [...backgrounds[]->{
  backgrounds[]{asset}
}]
// Nothing flattened

'thumbnails': [...backgrounds[]->{
  ...backgrounds[]{asset}
}]
// thumbnails: [ {}, {} ]

'thumbnails': [...backgrounds[]->{
  backgrounds[]{asset}
}.backgrounds]
// Nothing flattened
I'm guessing I'm missing something obvious here, but I can't seem to figure it out
AI Update

Based on your data structure, the solution to flattening your array of arrays in GROQ is:

'thumbnails': backgroundLists[]->.backgrounds[].asset

Here's how this works:

  1. backgroundLists[] - Iterates through your array of references
  2. -> - Dereferences each reference to get the actual document
  3. .backgrounds[] - Accesses the backgrounds array field within each document and iterates through it
  4. .asset - Extracts the asset object from each image

The key insight is that GROQ automatically flattens nested arrays when you chain array projections. Each [] operator iterates and flattens one level, so when you chain them like []->.someField[], GROQ effectively flattens multiple levels of nesting into a single flat array.

Based on the schema you're working with (an array of references to documents, where each document has a backgrounds array of images), the -> dereference operator is essential. It follows the reference to get the actual document content, then you can access the nested backgrounds array.

If your schema looks like this:

{
  title: 'Background list',
  name: 'backgroundList',
  type: 'document',
  fields: [
    {
      title: 'Backgrounds',
      name: 'backgrounds',
      type: 'array',
      of: [{ type: 'image' }]
    }
  ]
}

And you have an array field that references these documents, the query above will give you a flat array containing all the asset objects from all referenced background lists.

Important distinction: The -> operator is specifically for dereferencing - following references to other documents. This is different from just accessing nested arrays. In your case, since backgroundLists is an array of references (not direct nested arrays), you need the dereference operator to access the content of those referenced documents before you can flatten their internal arrays.

This pattern of chaining [] operators with -> is a common GROQ technique for flattening data structures that involve both references and nested arrays.

What does the schema itself look like that produces those object arrays?
export default {
  title: 'Background list',
  name: 'backgrounds',
  type: 'document',
  fields: [
    {
      localize: false,
      title: 'Name',
      description: 'A short name describing what this list of backgrounds contains.',
      name: 'text',
      type: 'string',
    },
    {
      title: 'Backgrounds',
      name: 'backgrounds',
      type: 'array',
      of: [{ type: 'image', options: { accept: 'image/*' } }],
    },
  ],
};

I think because the assets are similar to a kind of reference, in the sense that they can be "pushed forward" to resolve their constituent elements, something like asset-> might be more effective than spread syntax or leaving the word asset by itself.
I am far more intrigued by a document type and a field sharing name. I am sure it can tell the difference based on type and requested hierarchy but in case it can't, I wonder if you could resolve the query for fields to something more...explicit? Like setting your own projection name, but mapping it to the document with an @ or ^, depending on the context?

I apologize that I couldn't help easily this time; hopefully someone more knowledgeable will be along soon.
Thanks for the replies User and User! (Note I've changed some names to see if the scoping thing you mentioned changes anything,
user S
)
'backgroundLists': backgroundLists[].asset->,
as you suggest
user M
gives me
"backgrounds": [
  null,
  null
]
which makes sense since
backgroundLists
is an array of references to an array of images, and you of course simply wanted to point me in the right direction, it works with something like
'backgroundLists': backgroundLists[]->.backgrounds[].asset,
. Thanks again, both of you. Going to check whether the scoping thing actually made any difference too.
Shared naming seems to make no difference πŸ‘
(other than being far more readable with more specific names πŸ˜…)
Interesting. After reading the thread top to bottom I was going to next try the last bit except ending in []->.asset and see if that worked to open it up then crawl it at the same time. I like GROQ but I'm still learning myself. I would love in some of the documentation if it would stay in the context of one kind of attempted use case so the pivots between different syntax had added clarity for situations like this, because I'm realizing how soon in the process my next step would be to just bang on it with trial and error.
I am going to try to reproduce this schema in a test project so I can do that. Hopefully later tonight unless you're able to find a solution by then.

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?