Nesting ld+json partials

I’m going nuts with blocks that are nested and containing ld+json code. I have prepared the structures I need in json format, in the form of blocks in which I insert the variables.
The problem is that Hugo sometimes doesn’t quote with quotes, or decides to escape characters.
He sometimes he just does it before or after a reboot, or site build; and Google Search Console reports that the structured data is invalid.

A technique that apparently seems to work is to create dicts and transform them into json, but before doing all the work again, I would like to make sure I haven’t done something simple and fundamental wrong.

This is the structure:

baseof.html > head.html > markup-base.html

markup-base.html

{{- if eq .Page.Type "teacher" -}}
<script type="application/ld+json">
{
   "@context": "https://schema.org",
....
{{- block "schema-partial-teacher" . }}{{ end -}}
...

And here’s the problem:

schema-partial-teacher.html

{{ define "schema-partial-teacher" -}}
{{- $image = .Params.featured_image -}}
{{- $imgFull = print .Permalink .Params.featured_image }}
  
     {"@type": "Person",
       "name": "person name",
       "image": {
               "@type": "ImageObject",
               "url": "{{ $imgFull }}",
...

the result is

"url": "http:\/\/localhost\/person.webp"

So I tried these too, without success:

"url": {{ $imgFull }} <- without quotes, sometimes it works sometimes it doesn't
"url": "{{ $imgFull | jsonify }}
"url": "{{ $imgFull | safeHTML }}

Maybe I should try creating dicts and turning them into json:

{{- $addressCountry := dict
   "@type" "Country"
   "name" "Italy"
   "alternateName" "IT"
-}}

{{- $address := dict
"@type" "PostalAddress"
"streetAddress" "address street, 1"
"addressLocality" "City"
"postalCode" "ZIP"
"addressCountry" $addressCountry
-}}
...

until completing the part that interests me, so that in markup-base.html I have this:

{{ print $organization | jsonify }}

And maybe that’s right way, but before doing it all over again, I wanted to know if there’s a right way to write partials directly in json format, in which to integrate variables, without having to create dictionaries and transform them.

Thanks

I learnt this from Joe Mooring and it has been a life saver. I would suggest you try it too.

Anyway, Hugo adds quotes to URL’s automatically in JSON when you invoke the .Permalink. So, change "url": "{{ $imgFull }}", to "url": {{ $imgFull }},

@Arif Thanks for confirming that it’s the right way.

The problem is not with “url”, but also with “description” (taken from the parameters in the frontmatter), “featured_image”, and so on.

Especially the fact that Hugo doesn’t always generate the same things; because if the schematic comes out just once, then it must come out just the second time too… whereas instead sporadic errors occur, and force to redo the job.

I’ve also noticed that the behavior is a bit different if you put json code in partials or in blocks… so I hope I don’t have any surprises doing so.

Maybe it was the right way, It’s fine to force everyone (at least, those who want to appear in their engine, and there’s not much choice) to use schema.org, but at least Google could make a tester that works well. Or leave it to schema.org.

Hugo generates json with the keys sorted alphabetically, so that

  1. The schema.org tester validates the ld+Json code, all good
  2. Google’s tester for the same ld+json code reports in red that three fields are missing, and goodness concedes that one is optional.
  3. Other json code validators I’ve tried, don’t report errors (and the required fields are there)

So the Google tester is wrong, but I have to find the remedy otherwise it won’t position me, and he starts reporting in the Search Console that the structured data is invalid.

So now with Hugo I understand that:

  1. I can’t write a partial with json code, because unfortunately I can’t trust that Hugo doesn’t process it badly.
  2. Making dicts in partials and converting them to json is perfect, but not good for Google; so it’s not good.

THE QUESTION:

  1. Is there another way to get json script from nested partials, that is reliable? If I used data files in yaml format, read them in partials and converted to json would there still be the problem of alphanumeric reordering from point 2?

(But am I the only one using nested blocks to generate ld+json metadata? How do you guys do it?)

Thank you

Can’t tell without seeing your project, but to nest e.g image, you need to create a dict of its elements too

{{- $item := dict 
   "@type": "Person"
   "name": "person name"
   "image" ( dict
     "@type": "ImageObject"
     "url": {{ $imgFull }}
  )
}}

<script type="application/ld+json">
  {{ jsonify (dict "indent" " ") $item | safeJS }}
  </script>
  {{- end }}

I think this is not a Hugo issue, but the way you are generating the schema. You can share your repo or a test repo.

@Arif In the meantime, thank you for the time you are dedicating to me. I don’t know if it’s the way I generate the schemas that’s wrong, but for schema.org it’s fine, for any formal validator it’s fine, while Google reports “fields not found”.
And unfortunately the only thing that matters is what Google likes.

Hugo’s only “problem” here seems to me to be the alphabetical sort which does produce json code from dicts.

Of course I created the nested structures as you wrote, that’s not a problem (it’s just ugly to look at and to handle, compared to clean and indented json code).

Anyway, here’s the rub: I’m sorry for the amount of code but I avoid cutting stuff. There are 3 SportsActivityLocations, generated via 3 partials.
Two of them I modified to use the dict + json technique.
You know right away what they are, it’s the only part without indentation.
You can take all this code and see for yourself the errors that Google reports, just because of the alphabetical reordering of the keys.

https://validator.schema.org
https://search.google.com/test/rich-results
https://jsonlint.com

Here paste Json code and partial code:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
    "@graph": [
      {"@type": "WebPage",
        "@id": "http://192.168.12.200:1313/",
        "url": "http://192.168.12.200:1313/",
        "name": "Scuola di danza e ballo a Milano",
        "description": "Il Mosaico Danza è scuola di danza, ballo e culture a Milano. Offre corsi di flamenco, lindy hop, salsa, reggaeton, heels dance e tanto altro. Iscriviti oggi!",
        "license": "https://creativecommons.org/licenses/by-nc/4.0/",
        "datePublished": "2022-06-29T21:35:09+02:00",
        "dateModified": "2023-03-28T23:01:15.3Z",
        "isPartOf": {
          "@id": "https://www.ilmosaicodanza.it/#website"
        },
        "about" : {
          "@id": "https://www.ilmosaicodanza.it/#organization"
        },
        "primaryImageOfPage": {
          "@id": "https://www.ilmosaicodanza.it/#primaryimage"
        },
        "image": {
          "@id": "https://www.ilmosaicodanza.it/#primaryimage"
        },
        "thumbnailUrl": "https://www.ilmosaicodanza.it/img/logo-il-mosaico-danza-asd.svg",
        "breadcrumb": {
            "@id": "https://www.ilmosaicodanza.it/#breadcrumb"
        },
        "inLanguage": "it-IT",
        "potentialAction": [
          {
              "@type": "ReadAction",
              "target":
              [
                  "https://www.ilmosaicodanza.it/"
              ]
          }
        ]
      },
      {"@type": "ImageObject",
        "inLanguage": "it-IT",
        "@id": "https://www.ilmosaicodanza.it/#primaryimage",
        "url": "http://192.168.12.200:1313/spettacolo-scuola-di-danza-il-mosaico.webp",
        "contentUrl": "http://192.168.12.200:1313/spettacolo-scuola-di-danza-il-mosaico.webp",
        "width":  1024 ,
        "height":  576 
      },
      {"@type": "BreadcrumbList",
        "@id": "https://www.ilmosaicodanza.it/#breadcrumb",
        "itemListElement": [
          {
            "@type": "ListItem",
            "position": 1,
            "name": "Home",
            "item": "https://www.ilmosaicodanza.it/"
          }
        ]
      },
      {"@type": "WebSite",
        "@id": "https://www.ilmosaicodanza.it/#website",
        "url": "https://www.ilmosaicodanza.it/",
        "name": "Il Mosaico Danza",
        "description": "La migliore scuola di danza a Milano",
        "publisher": {
          "@id": "https://www.ilmosaicodanza.it/#organization"
        },
        "inLanguage": "it-IT"
      },
      {
        "@type": "Organization",
        "name": "Il Mosaico Danza",
        "url": "https://www.ilmosaicodanza.it",
        "@id": "https://www.ilmosaicodanza.it/#organization",
        "logo": "https://www.ilmosaicodanza.it/img/logo-il-mosaico-danza-asd.svg",
        "description": "Il Mosaico Danza è una scuola di danza a Milano che offre corsi di danza classica, moderna, contemporanea, hip hop e yoga per bambini, ragazzi e adulti di ogni livello. La nostra missione è offrire un ambiente accogliente e stimolante per lo sviluppo delle abilità fisiche e artistiche dei nostri allievi, promuovendo al tempo stesso il benessere psicofisico e la creatività.",
        "legalName": "Il Mosaico Danza a.s.d.",
        "slogan": "La migliore scuola di danza a Milano",
        "image": "https://www.ilmosaicodanza.it/spettacolo-scuola-di-danza-il-mosaico.webp",
        "keywords": [
          "Scuola di danza",
          "Scuola di ballo",
          "Flamenco"
        ],
        "telephone": "02 58 31 79 62",
        "email": "info@ilmosaicodanza.it",
        "address": {
          "@type": "PostalAddress",
          "name": "Il Mosaico Danza a.s.d. sede Loreto (Sede principale con segreteria)",
          "streetAddress": "Via Pomezia 12",
          "addressLocality": "Milano",
          "postalCode": "20127",
          "addressCountry": "IT"
        },
        "aggregateRating": {
          "@type": "AggregateRating",
          "ratingValue": "4.8",
          "ratingCount": "32"
        },
        "location":[

{"@type":"SportsActivityLocation","address":{"@type":"PostalAddress","addressCountry":{"@type":"Country","alternateName":"IT","name":"Italy"},"addressLocality":"Milano","postalCode":"20127","streetAddress":"Via Privata Pomezia, 12"},"contactPoint":{"@id":"https://www.ilmosaicodanza.it/#segreteria","@type":"Organization"},"description":"Corsi di danza, ballo e movimento  in Zona Loreto Anno 2022/2023. I corsi si tengono dal lunedì al giovedì compresi e anche il sabato.","email":"info@ilmosaicodanza.it","geo":{"@type":"GeoCoordinates","latitude":"45.489828","longitude":"9.215991"},"image":{"@type":"ImageObject","height":856,"url":"http://192.168.12.200:1313/danza-in-zona-loreto/corsi-di-ballo-zona-loreto-milano-1528x856.webp","width":1528},"keywords":["Mosaico Danza","Milano Loreto","corsi di danza","ballo","movimento","adulti","Danza Classica","Danza Contemporanea","Hip Hop","Lindy Hop","Salsa","Boogie Woogie","Ginnastica Posturale","Yoga","Pizzica","Danze del Sud","prenotazione","lezione di prova"],"logo":"https://www.ilmosaicodanza.it/img/logo-il-mosaico-danza-asd.svg","memberOf":{"@id":"https://www.ilmosaicodanza.it/#organization","@type":"Organization"},"name":"Il Mosaico Danza sede Loreto (principale con segreteria)","openingHoursSpecification":["Mo 17:00-23:00","Tu 18:30-22:30","We 18:00-23:00","Th 17:00-23:00","Sa 11:00-13:30"],"priceRange":"35 - 1000","slogan":"La migliore scuola di danza a Milano","telephone":"+39 02 58 31 79 62","url":"https://www.ilmosaicodanza.it/danza-in-zona-loreto/"},
  {"@id":"https://www.ilmosaicodanza.it/#portaromana","@type":"SportsActivityLocation","address":{"@type":"PostalAddress","addressCountry":{"@type":"Country","alternateName":"IT","name":"Italy"},"addressLocality":"Milano","postalCode":"20135","streetAddress":"Via Gian Carlo Passeroni, 6"},"contactPoint":{"@id":"https://www.ilmosaicodanza.it/#segreteria","@type":"Organization"},"description":"Corsi di danza e movimento in zona Porta Romana! Il Mosaico Danza a.s.d. offre Flamenco, Tango Argentino, Danza del Ventre, Anatomia in pratica. Iscriviti ora!","email":"info@ilmosaicodanza.it","geo":{"@type":"GeoCoordinates","latitude":"45.449717","longitude":"9.204977"},"image":{"@type":"ImageObject","height":400,"url":"http://192.168.12.200:1313/corsi-in-zona-porta-romana/allieve-corsi-flamenco-552x400.webp","width":552},"keywords":["Corsi di danza","Ballo","Flamenco","Movimento","Zona Porta Romana","Dance Workout","Anatomia in pratica","Tango argentino","Pizzica","Danze del Sud","Danza del ventre","Percussioni arabe","Percussioni flamenche","Sevillanas","Milano","Allieve","Numero chiuso","Lezione di prova","Serenità","Positività","Divertimento","Socializzazione"],"logo":"https://www.ilmosaicodanza.it/img/logo-il-mosaico-danza-asd.svg","memberOf":{"@id":"https://www.ilmosaicodanza.it/#organization","@type":"Organization"},"name":"Il Mosaico Danza sede Porta Romana Flamenco","openingHoursSpecification":["Mo 16:30-21","Tu 17:00-22:30","We 12:00-13:30 15:30-17:00 19:00-23:00","Th 17:00-22:30","Fr 18:00-22:15"],"priceRange":"35 - 1000","slogan":"La migliore scuola di danza a Milano","telephone":"+39 02 58 317 962","url":"https://www.ilmosaicodanza.it/corsi-in-zona-porta-romana/"},
    
    {"@type": "SportsActivityLocation",
            "name": "Il Mosaico Danza sede Tibaldi (c/o Fennec Danza)",
            "url": "https://www.ilmosaicodanza.it/danza-in-zona-loreto/",
            "description": "\"Lezioni di ballo e danza per ragazzi e bimbi Zona Tibaldi Anno 2022/2023. I  corsi si tengono mercoledì e giovedì.\"",
            "keywords":  ["corsi di danza", "danza moderna", "bambini", "ragazzi", "Milano", "Zona Tibaldi", "Fennec Danza", "prenotazione obbligatoria", "pedagogia", "sviluppo psicomotorio"],
            "logo": "https://www.ilmosaicodanza.it/img/logo-il-mosaico-danza-asd.svg",
            "slogan" : "La migliore scuola di danza a Milano",
            "telephone": "+39 02 58317962",
            "email": "info@ilmosaicodanza.it",
            "priceRange": "35 - 1000",
            "memberOf": {
              "@id": "https://www.ilmosaicodanza.it/#organization"
            },
            "contactPoint": {
              "@id": "https://www.ilmosaicodanza.it/#segreteria"
            },
            "image": {
              "@type": "ImageObject",
              "url": "http:\/\/192.168.12.200:1313\/corsi-danza-moderna-zona-tibaldi\/corsi-danza-bambini-ragazzi-984x656.webp",
              "width": "984",
              "height": "656"
            },
            "address": {
              "@type": "PostalAddress",
              "streetAddress": "Via Pietro Pomponazzi, 1",
              "addressLocality": "Milano",
              "postalCode": "20141",
              "addressCountry": {
                "@type": "Country",
                "name": "Italy",
                "alternateName": "IT"
              }
            },
            "geo": {
              "@type": "GeoCoordinates",
              "latitude": "45.441891",
              "longitude": "9.179255"
            },
            "openingHoursSpecification": [
              "We 17:15-18:15",
              "Th 17:15-18:15"
            ],
            "sameAs": [
              "https://www.facebook.com/fennecdanza/",
              "http://www.fennecdanza.it/"
            ]
          }
        ],
        "knowsLanguage": [
          {"@type": "Language", "name": "Italian", "alternateName": "it"},
          {"@type": "Language", "name": "English", "alternateName": "en"},
          {"@type": "Language", "name": "Spanish", "alternateName": "es"}
        ],
        "contactPoint": {
          "@type": "ContactPoint",
          "@id": "https://www.ilmosaicodanza.it#segreteria",
          "telephone": "+39 02 58317962",
          "contactType": "Segreteria: customer service, reservation, billing support, sales support",
          "email": "info@ilmosaicodanza.it",
          "contactOption": "CustomerService",
          "areaServed": {
            "@type": "City",
            "name": "Milano",
            "address": {
              "@type": "PostalAddress",
              "addressCountry": "IT"
            }
          },
          "availableLanguage": [
            "Italian",
            "English",
            "Spanish"
          ],
          "sameAs": [
            "https://wa.me/393396173388",
            "tel:+393396173388",
            "tel:+393519459036"
          ],
          "hoursAvailable": [
            "Mo 17:00-23:00",
            "Tu 18:30-22:30",
            "We 18:00-23:00",
            "Th 17:00-23:00",
            "Sa 11:00-13:30"
          ]
        },
        "sameAs": [
          "https://www.instagram.com/ilmosaicodanza/",
          "https://www.facebook.com/ilmosaicodanzamilano",
          "https://www.youtube.com/@ilmosaicodanza"
        ],
        "knowsAbout": [
          "Flamenco",
          "Podcast Flamenco chiavi in mano",
          "Lyrical Arab Dance",
          "Lindy Hop",
          "Balboa Dance",
          "Blues Dance",
          "Collegiate Shag",
          "Danza Classica bambini",
          "Danza Moderna bambini",
          "Gioco Hip-Hop bambini",
          "Latin dance per teenagers",
          "Oriental Gipsy Fusion",
          "Bollywood Dance",
          "Hip-Hop",
          "Michael Jackson Style",
          "Reggaeton",
          "Dancehall",
          "Heels Dance",
          "Salsa",
          "Bachata",
          "Tango Argentino",
          "Pizzica Tarantella e Tammurriata",
          "Ballo liscio e Balli da sala",
          "Yoga",
          "Libera la danza"
        ]
      }
  ]
}
</script>

Partial code (only for section reported as error by Google)

{{ define "schema-partial-sports-romana-passeroni" -}}
{{- $description := "" -}}
{{- $image := "" -}}
{{- $imgFull := "" -}}
{{- $width := 1920 -}}
{{- $height := 1080 -}}
{{- with site.GetPage "corsi-in-zona-porta-romana" -}}
  {{- $image = .Params.featured_image -}}
  {{- $imgFull = print .Permalink .Params.featured_image }}
  {{- $img := .Resources.GetMatch $image -}}
  {{- $width = $img.Width -}}
  {{- $height = $img.Height }}
  {{ $description = trim .Params.Description "\n" }}
{{- end -}}
{{- $addressCountry := dict
  "@type" "Country"
  "name" "Italy"
  "alternateName" "IT"
-}}
{{- $address := dict
"@type" "PostalAddress"
"streetAddress" "Via Gian Carlo Passeroni, 6"
"addressLocality" "Milano"
"postalCode" "20135"
"addressCountry" $addressCountry
-}}
{{- $memberOf := dict
"@type" "Organization"
"@id" "https://www.ilmosaicodanza.it/#organization"
-}}
{{- $contactPoint := dict
  "@type" "Organization"
  "@id" "https://www.ilmosaicodanza.it/#segreteria"
-}}
{{- $image := dict
"@type" "ImageObject"
"url" $imgFull
"width" $width
"height" $height
-}}
{{- $openingHoursSpecification := slice "Mo 16:30-21" "Tu 17:00-22:30" "We 12:00-13:30 15:30-17:00 19:00-23:00" "Th 17:00-22:30" "Fr 18:00-22:15" -}}
{{- $geo := dict
"@type" "GeoCoordinates"
"latitude" "45.449717"
"longitude" "9.204977"
-}}
{{ $keywords := slice "Corsi di danza" "Ballo" "Flamenco" "Movimento" "Zona Porta Romana" "Dance Workout" "Anatomia in pratica" "Tango argentino" "Pizzica" "Danze del Sud" "Danza del ventre" "Percussioni arabe" "Percussioni flamenche" "Sevillanas" "Milano" "Allieve" "Numero chiuso" "Lezione di prova" "Serenità" "Positività" "Divertimento" "Socializzazione" -}}
{{- $sportsRomanaPasseroni := dict
"@type" "SportsActivityLocation"
"name" "Il Mosaico Danza sede Porta Romana Flamenco"
"url" "https://www.ilmosaicodanza.it/corsi-in-zona-porta-romana/"
"@id" "https://www.ilmosaicodanza.it/#portaromana"
"description" $description
"logo" "https://www.ilmosaicodanza.it/img/logo-il-mosaico-danza-asd.svg"
"keywords" $keywords
"slogan" "La migliore scuola di danza a Milano"
"telephone" "+39 02 58 317 962"
"email" "info@ilmosaicodanza.it"
"priceRange" "35 - 1000"
"memberOf" $memberOf
"contactPoint" $contactPoint
"image" $image
"address" $address
"geo" $geo
"openingHoursSpecification" $openingHoursSpecification
-}}
{{- $sportsRomanaPasseroni }}
{{- end -}}

So, here are the results from testing your JSON code above. The errors are well spelt out, including the section throwing the error

https://search.google.com/test/rich-results/result?id=v2ZRtj3uBbcGnGnSFZpB7Q

Include the name and image fields in the section of your code below. The optional address warning is also coming from around the same section of your code. Study the rich results section where the error/warning is occurring and address that too.

"contactPoint": {
          "@type": "ContactPoint",
          "@id": "https://www.ilmosaicodanza.it#segreteria",
          "telephone": "+39 02 58317962",
          "contactType": "Segreteria: customer service, reservation, billing support, sales support",
          "email": "info@ilmosaicodanza.it",
          "contactOption": "CustomerService",
          "areaServed": {
            "@type": "City",
            "name": "Milano",
            "address": {
              "@type": "PostalAddress",
              "addressCountry": "IT"
            }
          },

@arif first of all thank you. I’m working on it too now, and I just noticed that Google reports the lack of the “name” field for the “ContactPoint”.

Which is really very comforting, and I’m happy because I can stop racking my brain trying to find strange solutions.

About studying… this is my 3rd iteration of microdata on the site, so far I’ve never used “name” in the “ContactPoint” and it’s never been flagged as an error, neither by the google tester nor by the tester schema.org.

So I scrupulously checked the documentation, and in addition to not being indicated as mandatory, there are also two examples where it is not.

But that’s okay, my work is not to be thrown away and now I can go on until the next surprise :slightly_smiling_face:

Thanks for your support and feedback :pray:

I went through a similar thing when defining my schema with dicts. I included a new image schema field (I think the one on copyright) and the rich results test threw an error. I scrapped all those fields altogether.

I know you find them ugly, but the dicts to me have been a lifesaver (but indenting them is where it can be a pain). They also help you visualize the code format as it would appear on the frontend.

Tip: If you want to minify the code, just remove the space between the double quotes before the last bracket. {{ jsonify (dict "indent" "") $item | safeJS }}.

1 Like

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.