Google Issues with ld+json

Google Issues with ld+json

avk9641
Excursionist
17 0 4

I am having some issues raised with Google. I keep having the following issue: Parsing error: Missing '}' or object member name, specifically highlighted around the line in bold: {% for image in product.images %}. 

 

I am also have a product snippet issue for Either "offers", "review", or "aggregateRating" should be specified. 

would appreciate any help/suggestions.
below is my ld+json:

<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}"
},
{
"@type": "ListItem",
"position": 2,
"name": "Eyewear",
"item": "{{ shop.url }}/collections/spirit-eyewear"
},
{
"@type": "ListItem",
"position": 3,
"name": "{{ product.title }}",
"item": "{{ shop.url }}{{ product.url }}"
}
]
},
{
"@type": "Product",
"name": "{{ product.title }}",
"itemCondition": "https://schema.org/NewCondition",
"image": [
{% for image in product.images %}
"{{ image.src | img_url: '1024x1024' }}"{% unless forloop.last %},{% endunless %}
{% endfor %}
],
"description": "{{ product.description | strip_html | truncate: 300 | escape }}",
"sku": "{{ product.sku }}",
"mpn": "{{ product.metafields.custom.mpn }}",
"brand": {
"@type": "Brand",
"name": "{{ product.vendor }}"
},
{% if product.metafields.reviews.rating_value and product.metafields.reviews.review_count and product.metafields.reviews.review_count != '0' %}
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "{{ product.metafields.reviews.rating_value }}",
"reviewCount": "{{ product.metafields.reviews.review_count }}"
},
{% endif %}
{% if product.metafields.reviews.reviews_list.size > 0 %}
"review": [
{% for review in product.metafields.reviews.reviews_list %}
{
"@type": "Review",
"author": "{{ review.author }}",
"datePublished": "{{ review.date }}",
"reviewBody": "{{ review.body | strip_html | escape }}",
"reviewRating": {
"@type": "Rating",
"ratingValue": "{{ review.rating }}"
}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
],
{% endif %}
"offers": {
"@type": "Offer",
"url": "{{ shop.url }}{{ product.url }}",
"priceCurrency": "{{ shop.currency }}",
"price": "{{ product.price | money_without_currency }}",
"priceValidUntil": "{{ 'now' | date: '%Y-%m-%d' | date_modify: '+1 year' }}",
"availability": "https://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}",
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": {
"@type": "MonetaryAmount",
"value": "{{ product.shipping_price | default: '0' }}",
"currency": "{{ shop.currency }}"
},
"shippingDestination": {
"@type": "DefinedRegion",
"addressCountry": "US"
},
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": {
"@type": "QuantitativeValue",
"minValue": 1,
"maxValue": 2,
"unitCode": "DAY"
},
"transitTime": {
"@type": "QuantitativeValue",
"minValue": 1,
"maxValue": 4,
"unitCode": "DAY"
}
}
},
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"name": "30 Day Return",
"url": "https://merhoki.com/policies/ ",
"applicableCountry": "US",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnMethod": "https://schema.org/ReturnByMail",
"returnFees": "https://schema.org/FreeReturn"
}
},
"hasOfferCatalog": {
"@type": "OfferCatalog",
"name": "Current Promotions",
"itemListElement": [
{
"@type": "Offer",
"itemOffered": {
"@type": "Product",
"name": "{{ product.title }}",
"image": "{{ product.featured_image | img_url: '1024x1024' }}",
"description": "{{ product.description | strip_html | truncate: 160 | escape }}"
},
"priceCurrency": "{{ shop.currency }}",
"price": "{{ product.price | money_without_currency }}",
"eligibleQuantity": {
"@type": "QuantitativeValue",
"value": 2,
"unitCode": "C62"
},
"description": "Buy One, Get One Free – Automatically Applied at Checkout.",
"availability": "https://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}",
"url": "{{ shop.url }}{{ product.url }}"
}
]
},
"color": "{{ product.options_by_name['Color'].values | first }}",
"size": "{{ product.options_by_name['Size'].values | first }}",
}
]
}
</script>

Reply 1 (1)

mageplaza-cs
Shopify Partner
496 42 82

Hi @avk9641 

I am from Mageplaza - Shopify solution expert.

 

Problem 1: Parsing Error: Missing '}' or object member name
The root cause is because Liquid loops (like {% for image in product.images %}) are outputting inside JSON directly without ensuring valid JSON syntax.

In JSON, you can't have trailing commas after the last item.
If your {% for image in product.images %} loop outputs an extra comma, Google and other parsers will throw errors.

Your section:

"image": [
{% for image in product.images %}
"{{ image.src | img_url: '1024x1024' }}"{% unless forloop.last %},{% endunless %}
{% endfor %}
],

 is mostly correct, but outside of context, if product.images is empty, it could still cause syntax issues.

SOLUTION:

  • Make sure to only output this if there are images.
  • Also, you need to guard it better.

Here's a safer way:

{% if product.images.size > 0 %}
"image": [
{% for image in product.images %}
"{{ image.src | img_url: '1024x1024' }}"{% unless forloop.last %},{% endunless %}
{% endfor %}
],
{% endif %}

Important: notice the comma after the array ], — this needs to be carefully placed.
If no images, you don't want image: [] appearing incorrectly.

 

Problem 2: Missing "offers", "review", or "aggregateRating"
Google requires that at least one of these fields exists in the Product schema:

  • "offers"
  • "review"
  • "aggregateRating"

You have them in your code...
BUT the problem is:

You are wrapping "aggregateRating" and "review" inside {% if ... %}, so if there are no reviews, that part won't be rendered at all.

And offers is always there, but if any Liquid errors cause syntax invalidation (e.g., broken commas or quotes), Google will not see it properly.

 

Hidden Problems in Your Code:

  • You have a trailing comma before the closing } of the Product object.
    Example:
"size": "{{ product.options_by_name['Size'].values | first }}",
}
​

JSON must NOT have a trailing comma before the last }.
➔ Remove that final comma after "size"!

  • There's an unnecessary space in your Return Policy URL:
"url": "https://merhoki.com/policies/ ",
​

That space could break parsing. Remove it.

So, fixed version (partial), focusing on critical points:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "BreadcrumbList",
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "name": "Home",
          "item": "{{ shop.url }}"
        },
        {
          "@type": "ListItem",
          "position": 2,
          "name": "Eyewear",
          "item": "{{ shop.url }}/collections/spirit-eyewear"
        },
        {
          "@type": "ListItem",
          "position": 3,
          "name": "{{ product.title }}",
          "item": "{{ shop.url }}{{ product.url }}"
        }
      ]
    },
    {
      "@type": "Product",
      "name": "{{ product.title }}",
      "itemCondition": "https://schema.org/NewCondition",
      {% if product.images.size > 0 %}
      "image": [
        {% for image in product.images %}
          "{{ image.src | img_url: '1024x1024' }}"{% unless forloop.last %},{% endunless %}
        {% endfor %}
      ],
      {% endif %}
      "description": "{{ product.description | strip_html | truncate: 300 | escape }}",
      "sku": "{{ product.sku }}",
      "mpn": "{{ product.metafields.custom.mpn }}",
      "brand": {
        "@type": "Brand",
        "name": "{{ product.vendor }}"
      },
      {% if product.metafields.reviews.rating_value and product.metafields.reviews.review_count and product.metafields.reviews.review_count != '0' %}
      "aggregateRating": {
        "@type": "AggregateRating",
        "ratingValue": "{{ product.metafields.reviews.rating_value }}",
        "reviewCount": "{{ product.metafields.reviews.review_count }}"
      },
      {% endif %}
      {% if product.metafields.reviews.reviews_list.size > 0 %}
      "review": [
        {% for review in product.metafields.reviews.reviews_list %}
          {
            "@type": "Review",
            "author": "{{ review.author }}",
            "datePublished": "{{ review.date }}",
            "reviewBody": "{{ review.body | strip_html | escape }}",
            "reviewRating": {
              "@type": "Rating",
              "ratingValue": "{{ review.rating }}"
            }
          }{% unless forloop.last %},{% endunless %}
        {% endfor %}
      ],
      {% endif %}
      "offers": {
        "@type": "Offer",
        "url": "{{ shop.url }}{{ product.url }}",
        "priceCurrency": "{{ shop.currency }}",
        "price": "{{ product.price | money_without_currency }}",
        "priceValidUntil": "{{ 'now' | date: '%Y-%m-%d' | date_modify: '+1 year' }}",
        "availability": "https://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}",
        "shippingDetails": {
          "@type": "OfferShippingDetails",
          "shippingRate": {
            "@type": "MonetaryAmount",
            "value": "{{ product.shipping_price | default: '0' }}",
            "currency": "{{ shop.currency }}"
          },
          "shippingDestination": {
            "@type": "DefinedRegion",
            "addressCountry": "US"
          },
          "deliveryTime": {
            "@type": "ShippingDeliveryTime",
            "handlingTime": {
              "@type": "QuantitativeValue",
              "minValue": 1,
              "maxValue": 2,
              "unitCode": "DAY"
            },
            "transitTime": {
              "@type": "QuantitativeValue",
              "minValue": 1,
              "maxValue": 4,
              "unitCode": "DAY"
            }
          }
        },
        "hasMerchantReturnPolicy": {
          "@type": "MerchantReturnPolicy",
          "name": "30 Day Return",
          "url": "https://merhoki.com/policies/",
          "applicableCountry": "US",
          "returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
          "merchantReturnDays": 30,
          "returnMethod": "https://schema.org/ReturnByMail",
          "returnFees": "https://schema.org/FreeReturn"
        }
      },
      "color": "{{ product.options_by_name['Color'].values | first }}",
      "size": "{{ product.options_by_name['Size'].values | first }}"
    }
  ]
}
</script>

 

Please let me know if it works as expected!

Best regards

Mageplaza | Top-Rated Shopify Agency | Trusted by 230,000+ worldwide merchants


If our suggestion works for you, please give it a Like or mark it as a Solution!


Should you have any questions or concerns, feel free to contact us via consultant@mageplaza.com