Accessing an object's keys within one property by using them in another property

I'm struggling to find a solution for this seemingly simple issue. Let me break it down with a basic example:

const cookbook: CookBook = {
    ingredients: {
        "tomato": { vegetal: true },
        "cheese": { vegetal: false },
        "lettuce": { vegetal: true },
    },

    recipes: {
        "pizza": ["tomato", "cheese"],
        "salad": ["tomato", "lettuce"]
    }
}

type CookBook = {
    ingredients: {[key: string]: {vegetal: boolean}},
    recipes: {[key: string]: string[]}
}

As shown in the recipes property, the values are lists of ingredient strings. But how can I ensure that these strings correspond to keys in the ingredients object?

    ...
    recipes: {
        // The inclusion of "pineapple" here should be prohibited 
        // as it is not a key in the ingredients property.
        "pizza": ["tomato", "cheese", "pineapple"],
        ...
    }
}

Answer №1

You have the option to create a function with a generic parameter

const cookbook = createCookBook({
    ingredients: {
        "tomato": { vegetal: true },
        "cheese": { vegetal: false },
        "lettuce": { vegetal: true },
    },

    recipes: {
        "pizza": ["tomato", "cheese"],
        "salad": ["tomato", "lettuce"]
    }
})
const createCookBook =
    <T extends CookBook & IsValidCookBook<T>>
        (cookbook: Narrow<T>) => cookbook

type IsValidCookBook<T extends CookBook> = {
    ingredients: T['ingredients'],
    recipes: { [K in keyof T['recipes']]: (keyof T['ingredients'])[] }
}

type Narrow<T> = {
  [K in keyof T]
    : K extends keyof [] ? T[K]
    : T[K] extends (...args: any[]) => unknown ? T[K]
    : Narrow<T[K]>
};

playground

We are creating a correct ValidCookBook object based on the inferred Cookbook object and then comparing the two. This enhances the developer experience within the IDE.

Narrow is utilized to prevent TS from expanding the ingredients array in the recipes, although there may be cleaner methods available now.


The type-based approach appears as follows

const cookbook = {
    ingredients: {
        "tomato": { vegetal: true },
        "cheese": { vegetal: false },
        "lettuce": { vegetal: true },
    },

    recipes: {
        "pizza": ["tomato", "cheese"],
        "salad": ["tomato", "lettuce"]
    }
} as const;

{type Test = ValidateCookBook<typeof cookbook>}
type ValidateCookBook <T extends CookBook & IsValidCookBook<T>> = never;

type IsValidCookBook<T extends CookBook> = {
    ingredients: T['ingredients'],
    recipes: { [K in keyof T['recipes']]: readonly (keyof T['ingredients'])[] }
}

type CookBook = {
    ingredients: {[key: string]: {vegetal: boolean}},
    recipes: {[key: string]: readonly string[]}
}

playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a way to update components in Angular without affecting the current URL?

I want to update a component without changing the URL. For example, I have a component called register. When I visit my website at www.myweb.com, I would like to register by clicking on sign up. How can I display the register component without altering the ...

Error: TypeScript is flagging that you can only specify known properties in an object literal, and the property '...' does not exist in the type 'DeepPartial<Document>'

I've been working on building a basic controller API in NodeJS with TypeScript, but I'm encountering the error ts(2345) while trying to assign values to the model. This is my user model: import mongoose, {Schema} from 'mongoose' cons ...

There seems to be an issue with a potentially null object in an Angular project while trying to view a PDF file

IDENTIFY THE ERROR: printContents = document.getElementById('print').innerHTML.toString(); ON LINE 4: print(): void { let printContents!: string; let popupWin!: any; printContents = document.getElementById('print').innerHTM ...

The orderBy filter seems to be malfunctioning

I'm utilizing ngx-pipes from this webpage: https://www.npmjs.com/package/ngx-pipes#orderby Specifically, I am using the orderBy pipe. However, when I implement the orderBy pipe in my HTML, the information is not being ordered correctly (from minor t ...

Utilizing a configuration file with Vue's `use` method

Within my index.ts file, I am setting up the configuration for the Google reCAPTCHA component using a specific sitekey: Vue.use(VueReCaptcha, { siteKey: 'my_redacted_sitekey' }); This setup occurs prior to the initialization of the new Vue({ ...

Tips on optimizing NextJS for proper integration with fetch requests and headers functionality

I'm currently working on a NextJS project and following the official tutorials. The tutorials demonstrate how to retrieve data from an API using an API-Key for authorization. However, I've run into a TypeScript compilation error: TS2769: No ove ...

Angular class change on scroll

HTML <ion-app> <ion-content> <div #scrolledToElement class="second-block" [ngClass]="flag ? 'red' : 'green' "></div> </ion-content> </ion-app> CSS .second-block { margin ...

Encountering a Problem with HTTP Requests in Angular 2

Seeking assistance with a technical issue. My objective: Make a REST API call to retrieve JSON data and resolve an Angular 2 promise. ServerAPI built with Node.js/ExpressJS/Lodash Sample of server.js file: var express = require('express'); va ...

Utilize a single function across multiple React components to interact with the Redux store, promoting code reusability and

Currently facing a dilemma. Here is a snippet of code that updates data in the redux store from a function, and it functions smoothly without any issues. const handleCBLabelText = (position: string, text: string) => { dispatch({ type: ' ...

Function that yields promise result

I need help figuring out how to make this recursive function return a promise value. I've attempted various approaches, but they all resulted in the search variable ending up as undefined. public search(message: Message) { let searchResult: strin ...

In Javascript, what significance does the symbol ":" hold?

While exploring the Ionic framework, I came across the following code snippet: import { AlertController } from 'ionic-angular'; export class MyPage { constructor(public alertCtrl: AlertController) { } I'm curious about the significanc ...

Turn off slider trace animation?

Check out the slider component in MUI here: https://mui.com/material-ui/react-slider/ I'm currently exploring how to disable the animation on the nub so it moves instantly to the new position. Any advice on how to achieve this? ...

Accessing observable property in Angular 2 with TypeScript: A comprehensive guide

My observable is populated in the following manner: this._mySubscription = this._myService.fetchData(id) .subscribe( response => this._myData = response, ...

Should classes/interfaces/functions be consistently prefixed with the App acronym as a best practice?

As I delve into my Angular project, I find myself contemplating the idea of using the App acronym as a prefix for all Classes, Interfaces, and Functions. This practice is commonly seen in tag components, where adding the App acronym helps avoid conflicts i ...

Unexpected Union Type Return from Apollo Server

When I call my resolver to return a union type (either a User or an object with a message key and value of type String, such as my UserNotFoundError type), it always comes back with "__typename": "User". Why is this happening and how ca ...

Exploring the power of Next.js, Styled-components, and leveraging Yandex Metrica Session Replay

I'm currently involved in a project that utilizes Next.js and styled-components. In my [slug].tsx file: export default function ProductDetails({ product }: IProductDetailsProps) { const router = useRouter(); if (router.isFallback) { return ( ...

What is the method for defining a function within a TypeScript namespace?

Suppose there is a namespace specified in the file global.d.ts containing a function like this: declare namespace MY_NAMESPACE { function doSomething(): void } What would be the appropriate way to define and describe this function? ...

Fairly intricate React function component declaration with TypeScript

const withAuth = () => <OriginalProps extends {}>( Component: React.ComponentType<OriginalProps & IAuthContextInterface> ) => { } (withAuth()(PrivateRoute)) // this is how the HOC called Could someone simplify what this function d ...

Encountering issues in d3.js following the transition to Angular 8

After upgrading my Angular 4 app to Angular 8, I encountered an issue where the application works fine in development build but breaks in production build. Upon loading the application, the following error is displayed. Uncaught TypeError: Cannot read p ...

Challenges arise when employing reduce with data types in an object

I want to transform an object in a function so that all keys are converted from Camel case to Pascal case. My Declaration: export interface INodeMailerResponseLower { accepted: string[]; rejected: string[]; envelopeTime: number; messageTim ...