A guide on structuring connected properties in Typescript

I possess a collection of interrelated attributes:

 A = B * C
 B = A / C
 C = A / B

A, B, and C are all intrinsic to my model, implying the existence of a function that takes an incomplete model lacking one attribute and generates a complete model with all three specified.

Hence, there exists a classification known as a "solvable" model, which lacks 0 or 1 attributes, and an "unsolvable" model that is missing 2 or more. In other words, a solvable model can be transformed into a comprehensive model through utilization of the aforementioned functions (or without any alteration).

How should I incorporate these concepts into my type system? I made attempts using Partial or Pick to devise distinct types for each, but it proved overly detailed and caused issues in compiling other sections of my application reliant on these functionalities.

Answer №1

Uncertain if the code below qualifies as excessively verbose (due to using Pick<> internally), or if it encounters the same compilation issues mentioned previously, but:

type MissingOneProperty<O extends object> = {
  [K in keyof O]: Pick<O, Exclude<keyof O, K>>
}[keyof O];
type MissingAtMostOneProperty<O extends object> =
  O | MissingOneProperty<O>;

The concept is that

MissingAtMostOneProperty<O>
will either be O itself, or it will lack exactly one property from O. This approach likely only applies to object types without index signatures (is this important?).

If the model is defined like this:

interface Model {
  a: number,
  b: number,
  c: number
}

A function can be declared to accept only models lacking at most one property:

declare function processModel(
  validModel: MissingAtMostOneProperty<Model>
): Model;
processModel({ a: 1, b: 2 }); // acceptable
processModel({ b: 2, c: 0.5 }); // acceptable
processModel({ a: 1, c: 0.5 }); // acceptable
processModel({ a: 1, b: 2, c: 0.5 }); // acceptable
processModel({ a: 1 }); // error, missing "b" property
processModel({ b: 2 }); // error, missing "a" property
processModel({ c: 0.5 }); // error, missing "a" property
processModel({}); // error, missing "a" property

This seems logical to me.


To grasp how this functions, let's break down what

MissingAtMostOneProperty<Model>
evaluates to:

MissingAtMostOneProperty<Model> 

becomes, following the definition of MissingAtMostOneProperty:

Model | MissingOneProperty<Model>

which follows the definition of MissingOneProperty:

Model | {[K in keyof Model]: Pick<Model, Exclude<keyof Model, K>>}[keyof Model]

then mapping over the 'a', 'b', and 'c' properties of Model:

Model | {
  a: Pick<Model, Exclude<keyof Model, 'a'>,
  b: Pick<Model, Exclude<keyof Model, 'b'>,
  c: Pick<Model, Exclude<keyof Model, 'c'>
}[keyof Model]

considering that keyof Model equals 'a'|'b'|'c' and Exclude<T, U> removes elements from unions:

Model | {
  a: Pick<Model, 'b'|'c'>,
  b: Pick<Model, 'a'|'c'>,
  c: Pick<Model, 'a'|'b'>
}['a'|'b'|'c']

utilizing how Pick<> works, we get:

Model | {
  a: { b: number, c: number },
  b: { a: number, c: number },
  c: { a: number, b: number }
}['a'|'b'|'c']

finally, by looking up a union of property keys in a type being equivalent to a union of the types of the properties, and per the definition of Model, results in:

{a: number, b: number, c: number} 
  | { b: number, c: number }
  | { a: number, c: number }
  | { a: number, b: number }

There you have it! The outcome is a union of Model along with all possible ways to remove one property from Model.

Hope this provides some insight. Best of luck!

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

Encountering incorrect month while utilizing the new Date() object

My Objective: I am looking to instantiate a new Date object. Snippet of My Code: checkDates (currentRecSec: RecommendedSection){ var currActiveFrom = new Date(currentRecSec.activeFrom.year,currentRecSec.activeFrom.month,currentRecSec.activeFrom.day ...

Storing information from JSON into an object

I am encountering an issue regarding transferring data from JSON to an object. Although this solution is functional, it is not entirely satisfactory. Take a look at the service. Is there an improved method for handling data conversion from this JSON to an ...

solution for downloading large files from authenticated endpoint that works across multiple web browsers

I'm currently on the lookout for a solution to download large files (around 2 - 4 GB) from a bearer token protected api endpoint that is compatible with all common browsers including IE 11, Chrome, Firefox, Android Browsers, and Safari. I want it to w ...

Guide to transforming API Response into Custom type in Angular 5

Describing my method to structure the API Response - interface MyTest { property2: string } Incorporating Angular 5 Service Code - getAPI(searchKey: string) { this.productsAPIUrl = https://localhost:44331/api/SampleData/WeatherFore ...

Typescript error in React: The element is implicitly of type any because a string expression cannot be used to index type {}

I'm currently working on grouping an array by 'x' in my React project using TypeScript, and I've encountered the following error message: Element implicitly has an 'any' type because expression of type 'string' can&a ...

Ways to retrieve the most recent update of a specialized typing software

When attempting to run typings install in a sample project with the below typings.js file, I received a warning. How can we determine the latest version number and what does the number after the + symbol signify? { "globalDependencies": { "core-js ...

Substitute data types for certain keys within a Typescript interface

Within this code snippet, the goal is to create a new type from an existing one by iterating through the keys and only replacing those that meet a specific condition. Additionally, union types are being utilized here. class A {} class B { constructor ...

Creating a unique Nest.js custom decorator to extract parameters directly from the request object

I am working with a custom decorator called Param, where I have a console.log that runs once. How can I modify it to return a fresh value of id on each request similar to what is done in nestjs? @Get('/:id') async findUser ( @Param() id: stri ...

In TypeScript, what do we call a property that is accessed dynamically?

I have a reusable function that can be used on various properties of a custom type. Here's an example: interface MyType { prop1: string; prop2: number; } type MyTypeKey = keyof MyType; const testValue = ( obj: MyType, property: MyTypeKey, v ...

What can cause a problem with the reduce function that populates an empty object with keys in TypeScript?

I've encountered an issue with a function that is meant to reduce an object. The problem lies in using the reduce method to assign the values of acc[key] as object[key], which is resulting in errors in the code. I am trying to avoid using any specific ...

Iterating through a for loop in Angular2 to send multiple GET requests to a Django backend

Currently, I'm facing a challenge with performing multiple GET requests using Angular2 within a Django/Python environment. After successfully making an API request and retrieving a list of users to determine the current user's ID, I utilize a .f ...

Trouble securing an undefined value in a union type

Encountering an error with the code below that seems unexpected. TypeScript is flagging rules[name] as not being callable, which is true since it can be undefined. Even after guarding against this case, the same error persists. The issue is elaborated in t ...

Guide on streamlining interface initialization within a React project using Typescript

Working on my Typescript project, I consistently utilize an interface within the State of various components: interface Item { selectedValue: string originalSelectedValue: string loading: boolean disabled: boolean isValid?: boolean } ...

Display a separate component within a primary component upon clicking a button

Looking to display data from a placeholder module upon component click. As a beginner with React, my attempts have been unsuccessful so far. I have a component that lists some information for each element in the module as a list, and I would like to be ab ...

React app's compilation is failing due to non-compliant ES5 code generation of the abab module, resulting in errors on IE

Can anyone explain why a create-react-app project using TypeScript and configured to generate ES5 code is not functioning on IE11 due to the "atob" function from the 'abab' package not being compiled into ES5 compliant code? module.exports = { ...

What is the purpose of manually creating the vscode.d.ts file in the vscode source code?

Manually writing .d.ts files is typically only necessary when working with existing .js files. If you're developing a TypeScript project, it's recommended not to write .d.ts files by hand, as the compiler with the --declaration option can auto-ge ...

The execution time of Node's Promises.all() function is unreasonably slow

I need to add a table containing data on sent emails after each email has been successfully sent. Within a loop, I am populating an array to be resolved using the Promise.all(). insertData is a function that adds data, requiring two parameters: connector, ...

Sanity.io and using images with next/image extension glitch

Hello everyone, I'm excited to join Stack Overflow for the first time. Currently, I am working on a project using Next.js with Sanity as my headless CMS. I have come across what appears to be a TypeScript type issue. Here is the link to the code on Gi ...

Could adjusting the 'lib' compiler option to incorporate 'dom' potentially resolve TS error code TS2584?

My preferred development environment is Visual Studio where I write Typescript code. I am facing an issue where I simply want to use the command "document.write" without encountering an error. Unlike my previous PC and VS setup, I am now getting an error ...

Methods for organizing consecutive elements within an array in Javascript/Typescript

Let's explore this collection of objects: [ { key1: "AAA", key2: "BBB" }, { key1: "BBB", key2: "CCC" }, { key1: "CCC", key2: "DD ...