Typescript: defining an interface that inherits properties from a JSON type

When working with TypeScript, I've utilized a generic JSON type as suggested in this source:

type JSONValue = 
 | string
 | number
 | boolean
 | null
 | JSONValue[]
 | {[key: string]: JSONValue}

My goal is to cast interface types matching JSON to and from the JSON type. For instance:

interface Foo {
  name: 'FOO',
  fooProp: string
}

interface Bar {
  name: 'BAR',
  barProp: number;
}

const genericCall = (data: {[key: string]: JSONValue}): Foo | Bar | null => {
  if ('name' in data && data['name'] === 'FOO')
    return data as Foo;
  else if ('name' in data && data['name'] === 'BAR')
    return data as Bar;
  return null;
}

Unfortunately, TypeScript currently raises an error because it doesn't recognize how the interface could match JSONValue:

Conversion of type '{ [key: string]: JSONValue; }' to type 'Foo' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
  Property 'name' is missing in type '{ [key: string]: JSONValue; }' but required in type 'Foo'.

However, logically we know that at runtime types Foo and Bar are indeed compatible with JSON. So, how can I convince TypeScript that this casting is valid?

ETA: While I can follow the error message and cast to unknown first, I prefer not to do that -- I'd like TypeScript to understand the distinction. Is that achievable?

Answer №1

The problem arises when the compiler fails to utilize the check

if ('name' in data && data['name'] === 'FOO')
for narrowing down the type of data from its original state of {[key: string]: JSONValue}. The type {[key: string]: JSONValue} is not a union, and at present, the in operator checks only narrow values of union types. There is an ongoing feature request at microsoft/TypeScript#21732 to enable such narrowing, but as of now, it's not included in the language.

This means that data remains of type {[key: string]: JSONValue} even after the check. When you attempt to assert that data is of type Foo through data as Foo, the compiler sends a warning indicating that there may be a mistake since it doesn't perceive Foo and {[key: string]: JSONValue} as closely related types.

If you are confident in the validity of your check, you can follow the compiler's suggestion and perform a type assertion to an intermediary type that is linked to both Foo and {[key: string]: JSONValue}, like unknown:

return data as unknown as Foo; // okay

If this concerns you, you have the option to create your own user-defined type guard function that carries out the desired narrowing similar to what would happen with

if ('name' in data && data['name'] === 'FOO')
. Essentially, if that condition is met, we can conclude that data is of type {name: 'FOO'}, which is sufficiently correlated to Foo for a type assertion to be valid. Here's an example of a potential type guard function:

function hasKeyVal<K extends PropertyKey, V extends string | number |
  boolean | null | undefined | bigint>(
    obj: any, k: K, v: V): obj is { [P in K]: V } {
  return obj && obj[k] === v;
}

Instead of using

if ('name' in data && data['name'] === 'FOO')
, you would write
if (hasKeyVal(data, 'name', 'FOO'))
. The return type obj is {[P in K]: V} indicates that upon returning true, the compiler should narrow the type of obj to something featuring a property with a key of type K and a value of type
V</code. Let's put it to the test:</p>
<pre><code>const genericCall = (data: { [key: string]: JSONValue }): Foo | Bar | null => {
  if (hasKeyVal(data, 'name', 'FOO'))
    return data as Foo; // okay, data is now {name: 'FOO'} which matches Foo
  else if (hasKeyVal(data, 'name', 'BAR'))
    return data as Bar;  // okay, data is now {name: 'BAR'} which corresponds to Bar
  return null;
}

Now it functions correctly. The hasKeyVal() verification narrows down data to include a name property of the correct type, making it adequately tied to either Foo or Bar for the type assertion to succeed (even though the type assertion is essential since a value of type {name: 'Foo'} may not necessarily be a Foo if Foo contains additional properties).

Playground link to code

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

Saving a nullable value to a JSON file in C++ using std::optional

Currently, I am working with a std::optional<float> test; The task at hand is to save this value to a json file. The catch here is that since it is optional, the presence or absence of its value is unknown until runtime. As a solution, I attempted t ...

Can you demonstrate how to display a response message using an alert?

Looking to display the response message as an alert message. Below is my code snippet: parameters["FName"] = txtFirstName.text ?? "" parameters["LName"] = txtLastName.text ?? "" parameters["Email"] = txtEmailId.text ?? "" parameters["Mobile"] = txtMobileN ...

Adding a new column for each array element within a jQuery foreach loop is a simple task that

I have a request where I am receiving three arrays in JSON format and I want to showcase each array in its own column. How can I accomplish this task? Below is the $.post function: $.post("/booking/times", { id: $("#user_id").val(), ...

Using regular expressions, you can locate and replace the second-to-last instance of a dot character in an email address

I'm looking to replace the second last occurrence of a character in a given string. The length of the strings may vary but the delimiter is always the same. Here are some examples along with my attempted solutions: Input 1: james.sam.uri.stackoverflo ...

Extract the maximum price of a stock from a JSON URL by accessing Yahoo Finance historical data through PHP

Currently, I am in the process of learning how to extract data from a JSON file and facing some challenges. My goal is to plot this data on a graph. $url="https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20yahoo.finance.historicaldata%20where ...

Deleting elements from an array of objects in Angular Would you like help with

I have a JSON structure and I need to remove the entire StartGeotag object from the array. [{ CreatedDate: "2022-02-17T10:30:07.0442288Z" DeletedDate: null ProjectId: "05b76d03-8c4b-47f4-7c20-08d9e2990812" StartGeotag: { ...

Prevent the Icon in Material UI from simultaneously changing

I'm working on a table where clicking one icon changes all icons in the list to a different icon. However, I want to prevent them from changing simultaneously. Any suggestions on how to tackle this issue? Code: import React from 'react'; im ...

Tips for updating the version number in a non-integer JSON format

Every time I run this code, I want it to update the JSON file. The problem: 1. The version in the JSON file is stored as a string rather than an integer Solution: I plan to extract the version number, convert it to an integer by removing the periods, ...

The application's functionality is interrupted when router.navigate() is called within the .subscribe method

I am having an issue with user navigation on my application. After successfully signing in, users get redirected to the home page (/), but they are unable to navigate by clicking any links on that page. Upon further investigation, I discovered that moving ...

Do const generics similar to Rust exist in TypeScript?

Within TypeScript, literals are considered types. By implementing const-generics, I would have the ability to utilize the value of the literal within the type it belongs to. For example: class PreciseCurrency<const EXCHANGE_RATE: number> { amount ...

Store the selected checkbox values in an array when submitting in Ionic

One issue I am facing is that the checked checkboxes are returning true instead of the value of input (type="checkbox"). Array displaying responded checked or unchecked items I am unable to store this data in an array as needed. Additionally, I cannot sp ...

Top method for hosting a lone JSON document

After developing a RoR application, I encountered a scenario where a JSON file needed to be created and sent to a server. My concern now is finding the optimal way to host this file so it can be accessed quickly from any location without crashing under h ...

Discover how to access and manipulate JSON files in an Angular application using

Currently, I am diving into learning TypeScript with Angular and I'm interested in reading a JSON file. The structure of my JSON file is as follows: { "nb": "7", "extport": "1176",, "REQ_EMAIL": ...

Fetching JSON data from a URL and displaying it in a TextView

How can I parse and display this JSON data in Android Studio? I want each item to be shown individually in a textview. Thank you all for your help! The content is from a URL only. {"s":true,"code":0,"errors":[],"c":"2.54","y":"5.8","i":"2.9","x":"0"} ...

Getting the item that was clicked on a Chart in a PrimeNG chart within an Angular application can be achieved by following these

I am trying to implement a bubble chart and I would like the function to be called when a user clicks on one of the bubbles. What is the best way for me to pass the data to this function? https://i.stack.imgur.com/FYiSP.png <p-chart type="bubble" [da ...

Angular developers may encounter a dependency conflict while attempting to install ngx-cookie

I'm currently facing an issue while attempting to add the ngx-cookie package for utilizing the CookieService in my application. Unfortunately, I am encountering some dependency conflicts that look like the following: $ npm install ngx-cookie --save np ...

How to Transform JSON into a List of Lists Using Python

Looking for assistance in developing Python code that can convert JSON data into a list of lists. JSON DATA : [{ 'service_count': 12, 'service_name': 'jboss', 'service_type': &ap ...

Export Image as Json File

I am currently working on creating a browser extension and I am trying to figure out how to save images locally. My plan is to store the images in a json file, but I'm feeling quite lost on how to accomplish this. Below is the code I have so far, alth ...

Is there a way to confirm if my JSON main object contains its sub-objects in an Android application?

My JSON file is shown below: { status: true, version: "2.0.3", status_code: 200, expires: "1458121027.0", Etag: "1458121027.0", cache_key: "match|icc_wc_t20_2016_g14|overs_summary", data: { batting_order: [ ...

Utilize Python to consolidate data from various API requests

I am currently developing an application that will need to interact with multiple external APIs to gather information and then present the results to a client. The client interacts with the application through a web interface, submitting queries to the ser ...