GROQ query for array with mixed objects and references

4 replies
Last updated: Jun 20, 2023
I’m having some trouble constructing a GROQ query for an array that holds a mix of objects and references. This is not the actual schema (it’s way to large to paste here), but analogous to…
export const person = defineType({
  name: 'person',
  type: 'document',
  title: 'Person',
  fields: [
    defineField({
      name: 'name',
      type: 'string'
    }),
    defineField({
      name: 'contacts',
      type: 'array',
      of: [
        {
          name: 'phoneNumber',
          type: 'string',
        },
        {
          name: 'addresses',
          type: 'reference',
          to: [
            {type: 'address'}
          ]
        }
      ]
    })
  ]
})
I’m hung up on the query. This will pull up the addresses, but phoneNumbers are `null`…

*[_type == 'person'][0] {
  name,
  contacts[] -> {
    ...
  }
}
… and this will bring up the phoneNumbers but not the addresses…

*[_type == 'person'][0] {
  name,
  contacts[] {
    ...
  }
}
I’m stumped, any ideas?
AI Update

I can see the issue you're running into. When you have an array with mixed content (both primitive values like strings and references), you need to handle them differently in your GROQ query.

The problem is that the -> operator only works on references (objects with a _ref field), so when you use contacts[] ->, it tries to dereference everything in the array - which makes the plain strings become null. Conversely, contacts[] without the dereference operator gives you the raw reference objects instead of the actual address documents.

The solution is to use the coalesce function combined with the @ symbol (which refers to the current element in the array):

*[_type == 'person'][0] {
  name,
  contacts[] {
    "value": coalesce(@->, @)
  }
}

Here's what's happening:

  • @-> attempts to dereference the current array item (works for references)
  • @ returns the current item as-is (works for primitives)
  • coalesce returns the first non-null value, so it'll use @-> for references and fall back to @ for plain strings

If you want to preserve the structure and make it clearer what type each item is, you could use the select function with defined(_ref) to check if something is a reference:

*[_type == 'person'][0] {
  name,
  contacts[] {
    select(
      defined(_ref) => @->,
      @
    )
  }
}

Or for a flatter structure:

*[_type == 'person'][0] {
  name,
  "contacts": contacts[]{
    coalesce(@->, @)
  }
}

The key insight is that you need to conditionally apply the dereference operator based on whether the item is actually a reference. The coalesce(@->, @) pattern is the most common and elegant way to handle this mixed array scenario in GROQ, as mentioned in the GROQ reference documentation.

Show original thread
4 replies
When you’re querying a mixed-fields array, you could be explicit:

*[_type == 'person'][0] {
  name,
  contacts[] {
    phoneNumber,
    addresses->
  }
}
Or you could be more general:


*[_type == 'person'][0] {
  name,
  contacts[] {
    _type == 'reference' => @->,
    _type != 'reference' => @
  }
}

Interesting. Okay, I’ll give that a crack.
thank you
alright, got it now. The actual schema was heinously more complex than what I outlined above 🙂 thank you so much.

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?