Parse Metaobjects Utility
Overview
Create a utility function that helps users parse metaobject fields. Speed up development processes by helping users move more quickly from metaobject graphql queries to being able to consume the data in their app.
Current Approaches
Right now its common for teams/project to create their own utilities to parse the metaobject query response. Mainly so they can attach field keys directly to their parsedValues. It would be nice to standardize and provide it out the box.
Benefits
- Recursive References - Formatting metaobject fields gets a bit tricky especially when dealing with deeply nested metaobject references. This function will handle formatting deeply nested fields.
- Key/Value Formatting - A typical way to access field data in an app is through properties. Raw fields data is formatted as an array. This formatter would convert fields to an object so properties can more easily be accessed.
Usage
There are two primary ways that this function will be used:
-
Metaobject Query - A user has queried a specific metaobject via the api by specifying the handle or id. Once the data is returned they’d like to pass it through the
parseMetaobjecthelper to get the properly formatted fields. - Metafield Query - A user would like to format metaobjects that have been referenced via metafields. The assignment can happen directly to a top level metafield or could be included within the field set of another metaobject.
Example Dev Experience
Graph QL Query
The original graphql query for a specific metaobject. This example includes a nested metaobject reference to show how that might be parsed. It'll be common to have nested metaobjects 2-3 levels deep.
query pageBuilder($handle: String!) {
metaobject(handle: {type: "page_builder", handle: $handle}) {
type
fields {
key
type
value
reference {
__typename
...on Metaobject {
type
fields {
type
key
value
}
}
}
}
}
}
Query Result
The unformatted output from the graphql api.
{
"data": {
"metaobject": {
"type": "page_builder",
"fields": [
{
"type": "single_line_text_field",
"key": "name",
"value": "Homepage",
"reference": null
},
{
"type": "metaobject_reference",
"key": "hero",
"value": "gid://shopify/Metaobject/id",
"reference": {
"type": "hero",
"fields": [
{
"type": "single_line_text_field",
"key": "title",
"value": "Shop New Products"
},
{
"type": "file_reference",
"key": "image",
"value": "gid://shopify/MediaImage/id",
"reference": {
"image": {
"id": "gid://shopify/ImageSource/id",
"url": "https://cdn.shopify.com/s/files/id"
}
}
}
]
}
}
]
}
}
}
Trigger Function
The user would trigger the utility by passing the metaobject to the function.
parseMetaobject(queryResult.data.metaobject);
Output Option 1
Formatted output transforming fields into parsedFields. The fields within parsedFields will have the exact output of the parseMetafield utility.
{
"type": "page_builder",
"fields": <Original Fields Array>,
"parsedFields": {
"name": {
"type": "single_line_text_field",
"key": "name",
"value": "Homepage",
"parsedValue": "Homepage",
"reference": null
},
"hero": {
"type": "metaobject_reference",
"key": "hero",
"value": "gid://shopify/Metaobject/id",
"reference": <Original Reference Object>,
"parsedValue": {
"type": "hero",
"fields": <Original Fields Array>,
"parsedFields": {
"title": {
"type": "single_line_text_field",
"value": "Shop New Products",
"parsedValue": "Shop New Products",
"reference": null
},
"image": {
"type": "file_reference",
"value": "gid://shopify/MediaImage/id",
"parsedValue": {
"image": {
"id": "gid://shopify/ImageSource/id",
"url": "https://cdn.shopify.com/s/files/id"
}
},
"reference": <Original Image Reference Object>
}
}
},
}
}
}
Output Option 2
Transform the output to directly attach parsed values to the field keys. This streamlines and makes the data easier to access but does create a discrepency in the data structures between fields within metaobjects and normal fields that were parsed directly with the parseMetafield utility.
{
"type": "page_builder",
"fields": <Original Fields Array>,
"parsedFields": {
"name": "Homepage",
"hero": {
"fields": <Original Fields Array>,
"parsedFields": {
"title": "Shop New Products",
"image": {
"image": {
"id": "gid://shopify/ImageSource/id",
"url": "https://cdn.shopify.com/s/files/id"
}
}
}
}
}
}
Implementation
To implement this we need to do a few things. This draft pr attempts to get the bones in place for these changes. Feedback on approach is more than welcome!
- Include single metaobject reference types in the parseMetafield helper.
- Include list type metaobject references in the parseMetafield helper.
- Create a specific parseMetaobject helper. This would essentially decorate a metaobject with additional metafield properties and call parseMetafield helper with it.
Open Questions
- Are we ok with converting the parsedFields key to object syntax instead of an array? The downsides of an array is that even after parsing, the field values can't be referenced directly through chaining. This becomes even more complex when accessing deeply nested data.
- How much transformation is too much transformation of the parsedFields? One thing I like about Option 2 is it'll reduce the amount of chaining. Ex.
metaobject.parsedFields.hero.parsedValue.parsedFields.title.parsedValuebecomesmetaobject.parsedFields.hero.parsedFields.title. - Is there a world where a user can specify the field output (1 or 2) via param
parseMetaobject(metaobject: value, streamlined: true)?
We detected some changes in packages/*/package.json or packages/*/src, and there are no updates in the .changeset.
If the changes are user-facing and should cause a version bump, run npm run changeset add to track your changes and include them in the next release CHANGELOG.
If you are making simple updates to examples or documentation, you do not need to add a changeset.
TBH I'd like to take this even further … or at least provide some options …
Like most of the time you're just looking for as simple a data shape as possible.
Given this query, for a section of a page I was working on …
query Spotlight(
$country: CountryCode
$handle: String!
$language: LanguageCode
) @inContext(country: $country, language: $language) {
product(handle: $handle) {
spotlight: metafield(namespace: "section", key: "spotlight") {
reference {
... on Metaobject {
title: field(key: "title") {
value
}
featuredImage: field(key: "featured_image") {
reference {
... on MediaImage {
image {
altText
width
height
url
}
}
}
}
sale: field(key: "sale") {
value
}
bgColor: field(key: "background_color") {
value
}
product: field(key: "product") {
reference {
... on Product {
handle
title
variants(first: 1) {
nodes {
availableForSale
price {
amount
currencyCode
}
compareAtPrice {
amount
currencyCode
}
image {
id
altText
height
width
url
}
}
}
}
}
}
}
}
}
}
}`;
I'd get the following response…
{
"data": {
"product": {
"spotlight": {
"reference": {
"title": {
"value": "Builder Tote"
},
"featuredImage": {
"reference": {
"image": {
"altText": null,
"width": 1460,
"height": 1478,
"url": "https://cdn.shopify.com/s/files/1/0657/3811/3197/files/spotlight-image-home-2.jpg?v=1716832296"
}
}
},
"sale": {
"value": "true"
},
"bgColor": {
"value": "Light Gray"
},
"product": {
"reference": {
"handle": "get-ship-done-hat-2-1",
"title": "Get Ship Done Hat 2.0",
"variants": {
"nodes": [
{
"availableForSale": true,
"price": {
"amount": "15.0",
"currencyCode": "CAD"
},
"compareAtPrice": null,
"image": {
"id": "gid://shopify/ProductImage/34706198692013",
"altText": "A close up view of the front of a black Shopify Supply baseball cap with text \"Ship It\" stitched in white.",
"height": 1000,
"width": 1000,
"url": "https://cdn.shopify.com/s/files/1/0657/3811/3197/files/01-GSD-Hat-PDP.webp?v=1716828182"
}
}
]
}
}
}
}
}
}
},
"extensions": {
"context": {
"country": "CA",
"language": "EN"
}
}
}
All I really need / would like is a data shape like this …
{
"spotlight": {
"title": "Builder Tote",
"featuredImage": {
"altText": null,
"width": 1460,
"height": 1478,
"url": "https://cdn.shopify.com/s/files/1/0657/3811/3197/files/spotlight-image-home-2.jpg?v=1716832296"
},
"sale": true,
"bgColor": "Light Gray",
"product": {
"handle": "get-ship-done-hat-2-1",
"title": "Get Ship Done Hat 2.0",
"variants": [
{
"availableForSale": true,
"price": {
"amount": "15.0",
"currencyCode": "CAD"
},
"compareAtPrice": null,
"image": {
"id": "gid://shopify/ProductImage/34706198692013",
"altText": "A close up view of the front of a black Shopify Supply baseball cap with text \"Ship It\" stitched in white.",
"height": 1000,
"width": 1000,
"url": "https://cdn.shopify.com/s/files/1/0657/3811/3197/files/01-GSD-Hat-PDP.webp?v=1716828182"
}
}
]
}
}
}
So …
- Follow and throw out
referenceandreferences - Flatten
nodesandedges - Flatten
value
I understand some value in wanting to opt into more data — for example if you need to do specific logic based on the value type, for instance. So maybe that could be an argument?
@benjaminsehl I like that. I say we simple it down to make it as easy as possible. People can always use the raw response if they need extra metadata.
@benjaminsehl I agree with you, thats what I'm currently doing!
Going to be implemented in another PR