Skip to content

satisfies should work with imported / referenced values #54488

Open
@matthew-dean

Description

@matthew-dean

Suggestion

Right now, satisfies works on values and object literals right after they're declared. However, there are use cases when satisfies can't be used immediately, and the value is imported, such as JSON files.

🔍 Search Terms

JSON, typed JSON, satisfies JSON

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

In short, I'd like to be able to do this:

import book from 'book.json'

const foo = book satisfies BookResource

or, alternatively:

import book from 'book.json' with { satisfies: BookResource }

and have it work equal to:

const book = {
  // book fields
} satisifies BookResource

📃 Motivating Example

I have this structure in my book data JSON file:

{
  "data": [
    {
      "type": "book",
      "baseCode": "394b76ce-3ad9-4c83-b21d-d5eab7962249",
      "id": "394b76ce-3ad9-4c83-b21d-d5eab7962249",
      "url": "http://github.com/donec/dapibus/duis/at.jpg",
      "originalUrl": "http://qq.com/ante/ipsum/primis.json",
      "imageUrl": "http://dummyimage.com/246x100.png/5fa2dd/ffffff",
      "retailer": "Amazon",
      "title": "Dead End Drive-In",
      "author": "Vlad Cutmore"
    },
    {
      // more of the same schema
    }
  ]
}

And I have a type (interface) like:

interface BookResource {
  id?: string
  type: 'book'
  baseCode: string
  imageUrl: string
  title: string
  author: string
  originalUrl: string
  retailer: string
  url: string
}

(Note, in this scenario, the source data MUST be JSON.)

I want to make sure that if anyone modifies the JSON, they get a type error. However, doing the following results in an error:

import books from 'books.json'
// ...
const bookData = books.data satisfies BookResource[] // error - "Type {...} is not assignable to BookResource"

Now, I wasn't sure if this should be marked as a bug or feature request, because this works:

const blah = {
  type: 'book',
  baseCode: '394b76ce-3ad9-4c83-b21d-d5eab7962249',
  id: '394b76ce-3ad9-4c83-b21d-d5eab7962249',
  url: 'http://github.com/donec/dapibus/duis/at.jpg',
  originalUrl: 'http://qq.com/ante/ipsum/primis.json',
  imageUrl: 'http://dummyimage.com/246x100.png/5fa2dd/ffffff',
  retailer: 'Amazon',
  title: 'Dead End Drive-In',
  author: 'Vlad Cutmore'
} satisfies BookResource

In other words, the exact same value data, when applied with the satisfies keyword, causes an error depending on where the value is being checked, even though it's the same value.

What seems to be the case is that satisfies gently coerces the type (specifically to "book" instead of string) in the latter example, but fails to do so when the value is imported.

💻 Use Cases

Currently, I want to use this for JSON, but note that there are other cases where you might want to "check" and gently nudge the type at compile time the exact same way that happens with satisfies at assignment time.

Because, similar to the above example, this also causes an error:

const foo = {
  type: 'book',
  baseCode: '394b76ce-3ad9-4c83-b21d-d5eab7962249',
  id: '394b76ce-3ad9-4c83-b21d-d5eab7962249',
  url: 'http://github.com/donec/dapibus/duis/at.jpg',
  originalUrl: 'http://qq.com/ante/ipsum/primis.json',
  imageUrl: 'http://dummyimage.com/246x100.png/5fa2dd/ffffff',
  retailer: 'Amazon',
  title: 'Dead End Drive-In',
  author: 'Vlad Cutmore'
}
const bar = foo satisfies BookResource

In TypeScript / JavaScript terms, these should be equivalent statements, and TypeScript should be able to

  1. say that the value of foo satisfies the type of BookResource
  2. coerce bar to be of type: "book" vs type: string, just as it would if satisfies immediately followed the value.

1st note: obviously I could just use as BookResource instead of satisfies BookResource, but of course that bypasses the type-checker entirely, and defeats the purpose. Then, the JSON could have any data / shape whatsoever, with no errors provided.

2nd note: I can also sorta get there with satisfies Array<Omit<BookResource, 'type'> & { type: string }> but that's kind of gross, and I want to enforce that the type truly is "book" and not just a string. JSON type inference is helpful but it isn't perfect, and currently there's no way to actual tell it how to interpret string literal types.

Metadata

Metadata

Assignees

No one assigned

    Labels

    In DiscussionNot yet reached consensusSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions