io-ts: Defining mandatory and optional keys within an object using a literal union

I am currently in the process of defining a new codec using io-ts.

Once completed, I want the structure to resemble the following:

type General = unknown;
type SupportedEnv = 'required' | 'optional'

type Supported = {
  required: General;
  optional?: General;
}

(Please note that at this time, the specific form of General is not crucial)

The main objective is to generate the type based on both General and SupportedEnv.

My current implementation looks something like this:


const GeneralCodec = iots.unknown;

const SupportedEnvCodec = iots.union([
  iots.literal('required'),
  iots.literal('optional'),
]);

const SupportedCodec = iots.record(SupportedEnvCodec, GeneralCodec)

type Supported = iots.TypeOf<typeof SupportedCodec>; 

The type Supported requires keys for both mandatory and optional fields:

type Supported = {
  required: General;
  optional: General;
}

Is there a way to actually make the optional field truly optional?

I have experimented with intersections and partial types...however, I am struggling with the syntax when incorporating them into iots.record.

Could this be accomplished? Should I approach this situation from a different angle?

Answer №1

Enhanced Solution

By implementing a mapping function called partialRecord (using the dependency of io-ts on fp-ts), we can achieve the desired outcome.

import { map } from 'fp-ts/Record';
import * as iots from 'io-ts';

export const partialRecord = <K extends string, T extends iots.Any>(
  k: iots.KeyofType<Record<string, unknown>>,
  type: T,
): iots.PartialC<Record<K, T>> => iots.partial(map(() => type)(k.keys));
  
const GeneralCodec = iots.unknown;

const SupportedEnvCodec = iots.keyof({
  required:null,
  optional:null,
});

type SupportedEnv = iots.TypeOf<typeof SupportedEnvCodec>;
type RequiredEnv = Extract<SupportedEnv, 'required'>;

const RequiredEnvCodec: iots.Type<RequiredEnv> = iots.literal('required');

type OtherEnvs = Exclude<SupportedEnv, RequiredEnv>;
const OtherEnvsCodec: iots.KeyofType<Record<OtherEnvs, unknown>> = iots.keyof({optional:null});

const OtherEnvsRecordCodec = partialRecord<
  OtherEnvs,
  typeof GeneralCodec
>(OtherEnvsCodec, GeneralCodec);

const SupportedCodec = iots.intersection([
  iots.record(RequiredEnvCodec, GeneralCodec),
  OtherEnvsRecordCodec,

]);

type Supported = iots.TypeOf<typeof SupportedCodec>;

The resulting type is as follows:

type Supported = {
    required: unknown;
} & {
    optional?: unknown;
}

Previously Proposed Solution

I have found that by introducing an additional layer of abstraction, I can approach the desired result, although it is not ideal.

For instance:

const GeneralCodec = iots.unknown;

const SupportedEnvCodec = iots.union([
  iots.literal('required'),
  iots.literal('optional'),
]);

type SupportedEnv = iots.TypeOf<typeof SupportedEnvCodec>;
type RequiredEnv = Extract<SupportedEnv, 'required'>;

const RequiredEnvCodec: iots.Type<RequiredEnv> = iots.literal('required');

type OtherEnvs = Exclude<SupportedEnv, RequiredEnv>;

const OtherEnvsCodec: iots.Type<OtherEnvs> = iots.union([
  iots.literal('optional'),
]);

const SupportedCodec = iots.intersection([
iots.record(RequiredEnvCodec, GeneralCodec),
iots.partial({
  env: iots.record(OtherEnvsCodec, GeneralCodec),
}),
]);

type Supported = iots.TypeOf<typeof SupportedCodec>; 

This results in the type:

type Supported = {
    required: unknown;
  } & {
    env?: {
        optional: unknown;
    } | undefined;
}

Although this solution involves an extra layer, it effectively addresses the original question in a roundabout manner.

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

Looking to execute a service method within an authguard service?

I am a beginner with Angular and I am looking to invoke a service method within the authguard service. The specific service function that I need is as follows. Please note that I do not want to make any changes to this service function. loadOrganizations() ...

What is the method for referencing a subtype within an established type?

When working with React-native, I came across a component called FlatList which includes a property known as ListHeaderComponent. My question is how to specify the type of this property without having to manually copy and paste the original type. Currentl ...

Creating a completely dynamic button in Angular 6: A step-by-step guide

I have been working on creating dynamic components for my application, allowing them to be reusable in different parts of the program. At this point, I have successfully developed text inputs that can be dynamically added and customized using the component ...

The correct way to assign a value within an Angular Observable subscribe function

Having some trouble with a basic form using @angular/material (although the material aspect shouldn't make a difference) that is structured like this: <div *ngIf="user"> <form> <mat-form-field> <m ...

Tips for relocating the indicators of a react-material-ui-carousel

I am working with a carousel and dots indicators, but I want to move the indicators from the bottom to the circular position as shown in the image below. I attempted using a negative margin-top, but the indicators ended up being hidden. Is there another ...

What are some methods for utilizing the "name" attribute within React components?

About My Coding Environment Utilizing TypeScript and ReactJS The Issue with Using name as an Attribute Encountering the following error: Type '{ name: string; "data-id": string; "data-type": string; }' is not assignable to ...

Would you like to learn how to display the value of a different component in this specific Angular 2 code and beyond

Hey there, I need your expertise to review this code and help me locate the issue causing variable "itemCount" to not display any value in about.component.html while everything works fine in home.component.html. I am attempting to only show "itemCount" in ...

Moving SVG Module

My goal is to create a custom component that can dynamically load and display the content of an SVG file in its template. So far, I have attempted the following approach: icon.component.ts ... @Component({ selector: 'app-icon', templa ...

Minimize the quantity of data points displayed along the X-axis in a highcharts graph

After making an API call, I received data for a Highcharts graph consisting of an array of datetimes (in milliseconds) and corresponding values (yAxis). The data is fetched every 15 minutes and displayed on a mobile device. When viewing the data monthly, ...

What causes Node.js to crash with the Headers already sent Error while managing errors in Express?

My current project involves using Express to set up an API endpoint for user registration. However, I've encountered a problem where sending a request that triggers an error to this API endpoint causes my node.js server to crash. The specific message ...

The static side of the class `typeof _Readable` is erroneously extending the static side of the base class `typeof Readable`

I am currently developing a Discord bot using node/typescript. After running the typescript compiler on my code, I encountered this error: node_modules/@types/readable-stream/index.d.ts(13,15): error TS2417: Class static side 'typeof _Readable' ...

Upon hovering, icons for each project name are displayed when `mouseenter` event is triggered

My issue lies with the mouseenter function. I am trying to display icons specific to the project name I hover over, but currently, it displays icons for all projects at once. I want each project hovered over to show me its respective icons Below is some c ...

Customize the element of the root node of a MUI component using the styled()

I am trying to implement the "component" prop with a MUI component (such as ListItem) using the styled() API. However, I am facing an issue where it says that "component" is not a valid prop. Can someone guide me on how to correctly achieve this? I have se ...

What is the best way to dynamically implement text ellipsis using CSS in conjunction with Angular 7?

i need to display a list of cards in a component, each card has a description coming from the server. In my component.html, I am using a ngFor like this: <div [style.background-image]="'url('+row.companyId?.coverUrl+')'" class="img- ...

Experimenting with TypeScript code using namespaces through jest (ts-jest) testing framework

Whenever I attempt to test TypeScript code: namespace MainNamespace { export class MainClass { public sum(a: number, b: number) : number { return a + b; } } } The test scenario is as follows: describe("main test", () ...

Unraveling the Mystery of @Input and @Output Aliases in Angular 2

After researching about the @Input() and @Output() decorators, I discovered that we have the option to use an alias instead of the property name for these decorators. For example: class ProductImage { //Aliased @Input('myProduct') pro ...

The react-bootstrap module does not have any members available for export

I'm looking to incorporate the npm module react-bootstrap from this link into my Layout component, following the ASP.NET Core 2.1 template client project structure. The challenge I'm facing is that the template uses a .js file extension, but I pr ...

Encountered Runtime Issue of TypeError: undefined cannot be iterated (unable to access property Symbol(Symbol.iterator))

I've been working on a multiselect feature in my Next.js project, utilizing states and setting them accordingly. However, I keep encountering this specific error: https://i.stack.imgur.com/m8zuP.png whenever I click on this: https://i.stack.imgur.c ...

Creating an enum in TypeScript can be accomplished by using the enum

What transformations do enums undergo during runtime in the TypeScript environment? Fruit.ts enum Fruit {APPLE, ORANGE}; main.ts let basket = [Fruit.APPLE, Fruit.ORANGE]; console.log(basket); The resulting main.js file remains identical to the .ts ver ...

Developing a Universal Type in Typescript

Encountered an issue with generic types while working on a user-defined type(interface) structured like this: IList1: { prop1: string, prop2: number, prop3: string . . . } ILi ...