(TypeScript) Defining a Type Alias or Interface using an object literal

As I work on creating a schema for JSON messages to be transferred between the server and client, I have implemented an object literal template for serialization purposes. Here is an example of what it looks like:

type uint8 = number;

const schema1 = {
    type: 'object',
    fields: {
        type: { type: 'uint8' },
        title: { type: 'string', optional: true }
    }
};

// Can the below type be automatically defined based on schema1?
type schema1Type = {
    type: number,
    title?: string
};

const data1: schema1Type = {
    type: 1,
    title: 'Hello World'
};

While using the object literal schema works well for validating message format, I am looking to create a type alias that aligns with this template in order to ensure message format correctness during compile time. Is there a way to achieve this? Any hints or suggestions would be greatly appreciated. Thank you.

Answer №1

To start off, you'll need to define the types for your schema definitions. Here's a suggested structure:

interface SchemaOptional { optional?: boolean }

interface SchemaNumber extends SchemaOptional { type: 'uint8' | 'int8' | 'float' }
interface SchemaString extends SchemaOptional { type: 'string' }
interface SchemaObject extends SchemaOptional {
    type: 'object',
    fields: Record<string, SchemaEntry>
}

type SchemaEntry = SchemaObject | SchemaNumber | SchemaString

This set of interfaces assigns a specific type to each entry in the schema, with SchemaEntry serving as a union of all possible types.

Note that the SchemaObject includes fields, which is a collection of keys and other schema entries, enabling recursive nesting.


Next, we'll create a type to convert each entry to its appropriate data type.

type SchemaToType<T extends SchemaEntry> =
    T extends SchemaNumber ? number :

    T extends SchemaString ? string :

    T extends SchemaObject ? SchemaObjectToType<T> :

    never

This conditional type evaluates if T matches a certain entry type and returns the corresponding data type. For string or number entries, it directly provides those, while for object entries, it invokes the SchemaObjectToType type, as outlined below:

type SchemaObjectToType<T extends SchemaObject> = {
    [K in keyof T['fields']]:
        | SchemaToType<T['fields'][K]>
        | SchemaEntryIsOptional<T['fields'][K]>
}

The above mapping assigns data types to all properties within the fields object by utilizing the SchemaToType function to recursively determine their actual types.

The final line ensures optional requirements are appropriately handled. Let's delve into this supporting type:

type SchemaEntryIsOptional<T extends SchemaEntry> =
    T['optional'] extends true ? undefined : never

This type will return undefined if the property is optional, or never if it's mandatory. Combining with undefined mimics an optional feature adequately.


Let's run some simple tests:

type TestA = SchemaToType<{ type: 'string' }>
// string

type TestB = SchemaToType<{ type: 'uint8' }>
// number

type TestC = SchemaToType<{ type: 'object', fields: { a: { type: 'string' }}}>
// { a: string }

type TestD = SchemaToType<{ type: 'object', fields: { a: { type: 'string', optional: true }}}>
// { a: string | undefined }

Everything seems to be functioning well. What about your schema1 object?

const schema1 = {
    type: 'object',
    fields: {
        type: { type: 'uint8' },
        title: { type: 'string', optional: true }
    }
} as const;

type Schema1Type = SchemaToType<typeof schema1>
/*
type Schema1Type = {
    readonly type: number;
    readonly title: string | undefined;
}
*/

All appears to be working correctly!

Playground Link

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

Obtain the output of a single controller in a different controller within the Express framework

Seeking to invoke a function from one controller in another Controller1.js 2) Controller2.js Code within Controller1.js file: var Controller2= require('../controllers/Controller2.js'); exports.getlist = function (req, res, next) { Control ...

having difficulty transmitting parameter to angular directive

After assigning a collection to a source variable, I am trying to activate a third party control (bootstrap-select) using a directive that watches the assigned collection. angular .module('app').directive('bootstrapDropdown', ['$t ...

Steps to extract the file name prior to uploading and transfer it to a different text input box without using fakepath

I need help with uploading an image to a folder on the server and then retrieving the image name without the fakepath. Here is my code snippet for file upload in PHP: <?php if (($_FILES['my_file']['name']!="")){ // Specify where th ...

Conditional rendering with React.js in the DOM

Just starting out with React and encountering an issue with rendering using reactDom: index.js import ReactDOM from 'react-dom'; import A from 'components/A'; import B from 'components/B'; render(<A />, document.getEl ...

Unique Rails Magnific Pop-Up Featuring Edit Buttons for Individual Table Rows

Within my table of records, each row features an edit button that triggers a pop-up form utilizing the magnific pop-up gem. The goal is for clicking on the edit button to display a pop-up form containing the specific information of the record clicked. Howe ...

Nothing remains after the fall: coding void

I am facing an issue where my item becomes null after being dragged 2-3 times and dropped in a different place. I have included my code below and I can't seem to figure out where the mistake lies. Can you please review it and let me know what needs to ...

Express JS get method causing browser tab to hang with continuous loading

After deciding to create my first REST API using Express.js, I embarked on the journey of exploring this new technology. To kickstart the process, I opted to utilize the Express application generator by running npx express-generator. Following that, in th ...

Avoid losing focus on href="#" (preventing the page from scrolling back up to the top)

Is there a way to prevent an empty href link from causing the page to scroll up when clicked? For example, if I have: <a href="#">Hello world</a> And this "Hello world" link is in the middle of the page. When clicked, the URL would look like ...

Automate Spreadsheet Updates with Google Apps Script

I'm currently facing an issue while trying to paste csv data into a Google Sheet using the code provided below. Strangely, when I execute myFunction(), the data quickly appears in the Google Sheet but then vanishes instantly, almost as if sheet.clear( ...

How can I make sure the return statement in getServerSideProps is only executed once all fetching operations are finished?

Currently, I am able to retrieve a person's username and corresponding data object with just one fetch from firebase. Inside this data object, there is a property named "uploads," which contains an array of documentIDs representing posts uploaded by t ...

Is axios allowed to be used in this scenario?

As a newcomer to web development, I apologize in advance if my question seems basic or if the details provided are insufficient. Nevertheless, I hope you can assist me with the following query: Is it possible to execute an axios.post request within a vue. ...

Compress PDF documents directly from a given web address

I'm currently attempting to create a zip file containing multiple PDF files by using the archiver npm module. While I have successfully managed to zip files from local memory to my machine, I am facing difficulties when trying to use URLs in the fs.cr ...

Exporting types with the `export =` syntax in TypeScript

For a code base that utilizes typescript export = syntax, I am looking to incorporate a new feature. The current code exports a function while also adding properties to it: const f = () => {}; f.someVal = 123; f.someFunc = () => {}; export = f; This ...

Tips for accepting numerous images in a Angular 4 application through Web Api

As a newcomer to Angular 4 and Web API, I am currently working on uploading multiple images from an Angular 4 application to the Web API. While I have successfully received the images in the API and can see the count of uploaded images during debugging, I ...

Increasing Divs in React with Buttons

Just getting started with React/Next and I have a query that I need help with. I'm attempting to create a button that dynamically adds more divs in real-time. Here is the code snippet I have so far: import React from 'react' const Clown = ...

When exporting a Mongoose model, it mysteriously becomes undefined

I am encountering an issue when trying to import the DemandeTransports model from a specific file: //@/components/database/model/Schema.js import { Schema } from "mongoose"; import mongoose from "mongoose"; const userSchema = new Schem ...

Dealing with Sideways Overflow Using slideDown() and slideUp()

Attempting to use slideUp() and slideDown() for an animated reveal of page elements, I encountered difficulty when dealing with a relatively positioned icon placed outside the element. During these animations, overflow is set to hidden, resulting in my ico ...

The error encountered in the Node crud app states that the function console.log is not recognized as a

I am attempting to develop a CRUD application, however, I keep encountering an error message that states "TypeError: console.log is not a function" at Query. (C:\Users\Luis Hernandez\Desktop\gaming-crud\server\app.js:30:25) h ...

Utilizing Dropzone for file uploads in Node.js, Express, and Angular

I'm running into a bit of trouble trying to get the file recognized on the server side with node.js. Especially when trying to access request data, such as req.files or req.body If anyone has any advice, here are some code snippets: HTML: <form ...

What is the correct way to reuse sub-dependencies using NPM?

This inquiry primarily centers around the usage of react-admin, as indicated by the tags, but could also be applicable in other scenarios. In our case, we are utilizing react-admin which relies on @material-ui/core. This grants us the ability to incorpora ...