Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'@type' as array #189

Open
TomRadford opened this issue Mar 7, 2023 · 4 comments
Open

'@type' as array #189

TomRadford opened this issue Mar 7, 2023 · 4 comments

Comments

@TomRadford
Copy link

Was wondering if it would be possible to use the @type in an array.
My use case would be in the context of utilizing two types like this:
'@type': (['Car', 'Product']
currently working around this by casting to one of the types
'@type': (['Car', 'Product'] as unknown as 'Car')

Would be keen to hear if this would be possible?

@Eyas
Copy link
Collaborator

Eyas commented Mar 8, 2023

Yeah this is also discussed in #179. There are a few issues here.

In general, you can construct your own type as a workaround:

import {Car, Product} from 'schema-dts';

type CarProduct = Omit<Car & Product, '@type'> & { "@type": ["Car", "Product"] }

const c: CarProduct = {
    "@type": ["Car", "Product"],
    "name": "abc",
    "roofLoad": {"@type": "QuantitativeValue"},
};

The problem with generic support, however, is that it's really easy to multiple @types for different "leaf" types, but its harder if we're trying to create types that understand subtypes of each item in the array as well.

@curtisburns
Copy link

This doesn't seem to work with ArtGallery and Organization. I've just tried this solution, but apparently, none of the properties exist now - @id, name, sameAs, etc. Is there a workaround for this?

@curtisburns
Copy link

For anyone else facing the same issue, I've resorted to the following for now:

type ArtGalleryOrganization = {
  '@type': ['ArtGallery', 'Organization'];
  '@id': Exclude<Organization, string>['@id'];
  name: Exclude<Organization, string>['name'];
  url: Exclude<Organization, string>['url'];
  logo: Exclude<Organization, string>['logo'];
  image: Exclude<Organization, string>['image'];
  founder: Exclude<Organization, string>['founder'];
  description: Exclude<Organization, string>['description'];
  sameAs: Exclude<Organization, string>['sameAs'];
  address: Exclude<Organization, string>['address'];
  location: Exclude<Organization, string>['location'];
  openingHours: Exclude<ArtGallery, string>['openingHours'];
  event: Exclude<Organization, string>['event'];
};

Seeing as ArtGallery is a sub-class of Organization, I only really use the one specific property with the rest coming from Organization.

Also using the Exclude<T, U> method as referencing properties like Organization['sameAs] doesn't seem to work. Obviously not ideal if you need to be aware of all the properties available for Organization and ArtGallery, but not too big of an issue for me. If there's a better way to do this let me know! Cheers.

@dsbrianwebster
Copy link

dsbrianwebster commented Oct 31, 2024

1. Here is a work around we've been trying....

(Example Assumes React + Next.JS)

Component:

import { deepmerge } from 'deepmerge-ts';
import type { Thing, WithContext } from 'schema-dts';

export function MultiTypeSchema({ things }: { things: WithContext<Thing>[] }) {
  return (
    <script type='application/ld+json'>
      {JSON.stringify(
        deepmerge(
          ...things.map((thing) => {
            if ('@type' in thing && typeof thing['@type'] === 'string') {
              return {
                ...thing,
                '@type': [thing['@type']],
              };
            }

            return thing;
          }),
        ),
      )}
    </script>
  );
}

Example Usage:

  const siteSchema: WithContext<WebSite> = {
    '@context': 'https://schema.org',
    '@type': 'WebSite',
    name: 'Website Name',
    description: 'Website Description',
    inLanguage: 'en-US',
    url: 'https://website.com',
    potentialAction: {
      '@type': 'SearchAction',
      target: `https://website.com/search?q={search_term_string}`,
      query: 'optional',
    },
    sameAs: ['https://x.com/website'],
  };

  const orgSchema: WithContext<Organization> = {
    '@context': 'https://schema.org',
    '@type': 'Organization',
    logo: {
      '@type': 'ImageObject',
      url: 'logo-square.png',
      width: '1024',
      height: '1024',
    },
  };

   <MultiTypeSchema things={[siteSchema, orgSchema]} />

Resulting output 👌:

{
  "@context": "https://schema.org",
  "@type": [
    "WebSite",
    "Organization"
  ],
  "name": "Website Name",
  "description": "Website Description",
  "inLanguage": "en-US",
  "url": "https://website.com",
  "potentialAction": {
    "@type": "SearchAction",
    "target": "https://website.com/search?q={search_term_string}",
    "query": "optional"
  },
  "sameAs": [
    "https://x.com/website"
  ],
  "logo": {
    "@type": "ImageObject",
    "url": "logo-square.png",
    "width": "1024",
    "height": "1024"
  }
}

Image

2. Now the questions that must be asked...

I'm sure there are scenarios where the workaround above is far from a complete solution. For one thing, it makes my LSP start bogging ass, but it does seem to get us the merged result we need.

The only reason we went down this road was to satisfy an SEO specialist a client is working with who insists on multi-type schemas. I'm hoping someone can help us understand what is better about this single multi-type schema versus:

  1. A separate schema for each thing.
  2. Using @graph, which is supported by schema-dts without any hacking or workaround. I would personally prefer to go this way and would like to advocate for it but would like to understand the objective downsides, if any.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants