Tim's Weblog
Tim Strehle’s links and thoughts on Web apps, software development and Digital Asset Management, since 2002.
2016-05-30

Where do I put search result context in schema.org?

I’ve been advocating schema.org for DAM interoperability for a while now, but mostly from a theoretical perspective – I didn’t have any implementation experience to back my claims up. This is supposed to change now as we’re trying to base parts of our new DAM UI on the schema.org vocabulary.

But of course, where the rubber meets the road, there’s unexpected challenges. I had already figured out how to express core asset data using schema.org, but a real DAM UI’s central aspect is search. And searches don’t just consist of the found objects’ data, but also of context and controls (to borrow a phrase from Ruben Verborgh’s excellent Turtles all the way down).

Here’s some things I need to know to render a good search UI (along with related OpenSearch and Hydra property names where available):

  • total number of results (opensearch:totalResults, hydra:totalItems)
  • pagination info: page size (opensearch:itemsPerPage), current page number (opensearch:startIndex), links to first, previous, next, and last page (hydra:first, hydra:previous, hydra:next, hydra:last)
  • human-readable search description of the submitted search criteria; for example: 'peppa pig' in Books, date published: 2016
  • links (with labels) to related searches: Did you mean: … or See also: …
  • data for filters / faceted navigation: available attributes and their values with labels, counts and links

The problem starts with me not knowing where to put this search result metadata in a valid schema.org response. (I’ve asked on public-schemaorg@w3.org and LinkedIn already, no replies so far.)

Update: I did get helpful replies, see the bottom of this post for the final version.

schema.org has a pretty clever concept, the SearchAction, and it seems you can stuff search results into its result property in JSON-LD syntax like this (I’m not 100% sure it’s okay to have result point to multiple things; haven’t found a schema.org validator yet):

{
  "@context": "http://schema.org",
  "@type": "SearchAction",
  "actionStatus": "CompletedActionStatus",
  "query": "john doe",
  "result": [
    {
      "@type": "ImageObject",
      "name": "Photo of Jane Doe"
    },
    {
      "@type": "ImageObject",
      "name": "Photo of John Doe"
    }
  ]
}

schema.org actions can have “output constraints” (see Potential Actions) but as far as I can see, they only specify the properties returned for resulting objects, not additional metadata.

My best guess so far is something like this (not including the faceted navigation data):

{
  "@context":
  {
     "@vocab": "http://schema.org/",
     "opensearch": "http://a9.com/-/spec/opensearch/1.1/"
  },
  "@type": "SearchAction",
  "actionStatus": "CompletedActionStatus",
  "query": "john doe",
  "description": "'john doe' in 'Images'",
  "potentialAction": 
  [
    {
      "@type": "SearchAction",
      "name": "Did you mean 'jane doe'?",
      "target": "http://example.com/?q=jane+doe"
    },
    {
      "@type": "SearchAction",
      "name": "See also: 'dole'",
      "target": "http://example.com/?q=dole"
    },
    {
      "@type": "SearchAction",
      "name": "next",
      "target": "http://example.com/?q=john+doe&p=2"
    }
  ],
  "opensearch:totalResults": 35,
  "opensearch:startIndex": 1,
  "opensearch:itemsPerPage": 2,
  "result": 
  [
    {
      "@type": "ImageObject",
      "name": "Photo of Jane Doe"
    },
    {
      "@type": "ImageObject",
      "name": "Photo of John Doe"
    }
  ]
}

Does that make sense to you? (The potentialAction names aren’t suitable for machine interpretation yet.) Any help is appreciated!

P.S.: What’s the difference between SearchAction and FindAction? Am I supposed to use the latter for search results?

Update: Jarno van Driel proposed using ItemList (which has a numberOfItems property) in his mailing list reply, and a follow-up. Thanks! I’ll use this approach then (sadly, the itemListElement requires wrapping items in an additional ListItem for ordered lists):

{
  "@context":
  {
     "@vocab": "http://schema.org/",
     "opensearch": "http://a9.com/-/spec/opensearch/1.1/"
  },
  "@type": "SearchAction",
  "actionStatus": "CompletedActionStatus",
  "query": "john doe",
  "description": "'john doe' in 'Images'",
  "potentialAction": 
  [
    {
      "@type": "SearchAction",
      "name": "Did you mean 'jane doe'?",
      "target": "http://example.com/?q=jane+doe"
    },
    {
      "@type": "SearchAction",
      "name": "See also: 'dole'",
      "target": "http://example.com/?q=dole"
    },
    {
      "@type": "SearchAction",
      "name": "next",
      "target": "http://example.com/?q=john+doe&p=2"
    }
  ],
  "result": 
  {
    "@type": "ItemList",
    "numberOfItems": 35,
    "opensearch:startIndex": 1,
    "opensearch:itemsPerPage": 2,
    "itemListElement":
    [
      {
        "@type": "ListItem",
        "position": 1,
        "item":
        {
          "@type": "ImageObject",
          "name": "Photo of Jane Doe"
        }
      },
      {
        "@type": "ListItem",
        "position": 2,
        "item":
        {
          "@type": "ImageObject",
          "name": "Photo of John Doe"
        }
      }
    ]
  }
}