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

node-ts displays an error message stating, "Unable to locate the name '__DEV__' (TS2304)."

I recently inserted __DEBUG__ into a TypeScript file within my NodeJS project. Interestingly, in VSCode, no error is displayed. However, upon running the project, I encounter an immediate error: error TS2304: Cannot find name '__DEBUG__'. I att ...

Leveraging the ngFor local variable within nested elements

Is there a way to properly display a property of the local variable theme, such as theme.name? Below is an example of how my *ngFor is structured: <ul> <li *ngFor="#theme of themes"> <span>theme.name</span> </li> ...

Using an Angular interface for an HTTP request: The statusText message reads as "Error: Unable to Determine."

I've been working on calling an API as an example in Angular using an interface. The API I'm trying to access is located at https://jsonplaceholder.typicode.com/posts. Unfortunately, I encountered the following error message: ERROR HttpErrorResp ...

Encountered an issue when attempting to include a model in sequelize-typescript

I've been attempting to incorporate a model using sequelize-typescript: type AppMetaDataAttributes = { id: string; name: string; version: string; createdAt: string; updatedAt: string; }; type AppMetaDataCreationAttributes = Optional<App ...

What is the best way to make one element's click event toggle the visibility of another element in Angular 4?

Recently diving into Angular, I'm working on a feature where users can toggle the visibility of a row of details by simply clicking on the item row. The scenario involves a table displaying log entries, each with a hidden row underneath containing spe ...

Type '{}' is lacking the subsequent attributes in type 'Record'

This is the code snippet I am working with: const data : Record<MediaType, Person[]> = {}; I encountered an error while initializing the 'data' object as shown above. The error message specifies that certain properties are missing from typ ...

Next.js 14 useEffect firing twice upon page load

Having an issue with a client component in next js that is calling an API twice at page load using useEffect. Here's the code for the client component: 'use client'; import { useState, useEffect } from 'react'; import { useInView ...

Why is it that Angular is unable to locate the component factory for my component, even though I have declared it in the entryComponents option of my Module?

Is there a way to dynamically create and show a component in an ngx-bootstrap's modal? I attempted to declare the component in the entryComponents option of RegMktRiskHomeModule, but it did not work. Currently, I am only able to declare the Scenarios ...

Is there a way to modify the button exclusively within the div where it was pressed?

I plan to incorporate three buttons in my project (Download, Edit, and Upload). Upon clicking the Download button, a function is triggered, changing the button to Edit. Clicking the Edit button will then change it to Upload, and finally, clicking the Uplo ...

The absence of defined exports in TypeScript has presented a challenge, despite attempting various improvement methods

I've exhausted all available resources on the web regarding the TypeScript export issues, but none seem to resolve the problem. Watching a tutorial on YouTube, the presenter faced no such obstacles as I am encountering now. After updating the tsconf ...

A Promise is automatically returned by async functions

async saveUserToDatabase(userData: IUser): Promise<User | null> { const { username, role, password, email } = userData; const newUser = new User(); newUser.username = username; newUser.role = role; newUser.pass ...

Exploring the MVVM architecture in React and the common warning about a missing dependency in the useEffect hook

I'm currently in the process of developing a React application using a View/ViewModel architecture. In this setup, the viewModel takes on the responsibility of fetching data and providing data along with getter functions to the View. export default f ...

Tips for effectively generating a JSON object array in Typescript

Currently, I'm attempting to construct an array of JSON objects using TypeScript. Here is my current method: const queryMutations: any = _.uniq(_.map(mutationData.result, function (mutation: Mutation) { if (mutation && mutation.gene) { co ...

Setting up TypeScript in Jest without the need for webpack

Currently, I'm developing an NPM module using TypeScript without the use of Webpack for compiling scripts. I need some guidance on configuring Jest to properly run tests with TypeScript files. Any recommendations? // test.spec.ts import {calc} from ...

Transforming JavaScript code with Liquid inline(s) in Shopify to make it less readable and harder to understand

Today, I discovered that reducing JavaScript in the js.liquid file can be quite challenging. I'm using gulp and typescript for my project: This is a function call from my main TypeScript file that includes inline liquid code: ajaxLoader("{{ &ap ...

Access an external URL by logging in, then return back to the Angular application

I am facing a dilemma with an external URL that I need to access, created by another client. My task is to make a call to this external URL and then return to the home page seamlessly. Here's what I have tried: <button class="altro" titl ...

Checking a sequence using a list of strings

I have an array containing a list of IDs: var listId: string[] = []; var newId: boolean; for (let i in data.chunk) { listId.push(data.chunk[i].aliases[0]); } My objective is to compare a new ID with the entire list. If the new ID is found in the list ...

What causes an interface to lose its characteristics when a property is defined using index signatures?

Here's the code snippet I'm having trouble with, which involves tRPC and Zod. import { initTRPC, inferRouterOutputs } from '@trpc/server'; import { z } from "zod"; const t = initTRPC.create(); const { router, procedure } = t; ...

Struggling with importing aliases in TypeScript for shadcn-ui library

I am facing a challenge with resolving TypeScript path aliases in my project. I have set up the tsconfig.json file to include path aliases using the "baseUrl" and "paths" configurations, but alias imports are not functioning as intended. My goal is to imp ...

Guide to leveraging various build targets while executing the capacitor sync command within an Angular project

In my current Angular project, I have been utilizing Capacitor. Within my angular.json file, I have set up various build targets such as development, staging, and production. To deploy with different configurations, I use the command ng build --configurati ...