Monday, June 3, 2024
 Popular · Latest · Hot · Upcoming
152
rated 0 times [  155] [ 3]  / answers: 1 / hits: 6946  / 4 Years ago, thu, september 10, 2020, 12:00:00

I have a function that reduces an array of properties and builds an object:


type FieldProps = {
name: string;
value: string;
required: boolean;
styles?: object;
className?: string
}

const fields: FieldProps[] = [
{
name: "firstName",
value: "Bob",
required: true,
styles: { width: "100%"}
},
{
name: "lastName",
value: "Smith",
required: true,
className: "example"
},
{
name: "city",
value: "Tulsa",
required: true,
},
{
name: "state",
value: "OK",
required: true,
},
]

const parseFields = <T extends any[], K extends object = {}>(fields: T): K => {
try {
if (!fields || (fields && fields.length < 0)) throw new Error("You must supply an array of fields!");

const parsedFields = fields.reduce((acc, { name, value }: { name: string, value: string}) => {
switch (name) {
case "city":
case "street":
case "state":
case "suite":
case "zipCode": {
acc["address"] = acc["address"] || {};
if (value) acc.address[name] = value;
break;
}
default: {
acc[name] = value;
break;
}
}
return acc;
}, {} as K);

return parsedFields;
} catch (err) {
throw String(err);
}
};

const userDetails = parseFields(fields);

As of now, hovering over userDetails is displaying an empty object type({}), when it ideally should be:


{
"firstName": string,
"lastName": string,
"address": {
"city": string,
"state": string
}
}

How can I refactor the function to take the type FieldProps[] and have it return a dynamically typed object?


Typescript playground




Update


It's super close to being typed with the help of @Mingwei, but I'd essentially like to return the typeof ParseFieldsResult<T>. For example, I tweaked his playground and I'd like to return the typeof O. What's silly is that if I copy & move the type ParseFieldsResult<T> into the return, then it gives me the object type I'd expect see on userDetails (but with WET code): playground.


More From » typescript

 Answers
5

This answer is very (over)complicated, but you do end up with this type:


const userDetails = parseFields(fields);
type Z = typeof userDetails;
// type Z = {
// address: {
// city: "Tulsa" | "Norman"; // Added to test
// state: "OK";
// };
// } & {
// firstName: "Bob";
// lastName: "Smith";
// }

It requires making fields as const:


const fields = [
...
] as const;

And... the actual typings:


type AddressKeys = 'city' | 'street' | 'state' | 'suite' | 'zipCode';

type _ParseFieldsResultKeys<T extends readonly FieldProps[]> = {
[K in keyof T]: T[K] extends { name: string } ? T[K]['name'] : never;
}[Exclude<keyof T, keyof []>];

type _GetParseFieldResultValue<T extends readonly FieldProps[], N> =
Extract<T[keyof T], { name: N }> extends { value: infer V } ? V : never;

type ParseFieldsResult<T extends readonly FieldProps[]> = {
address: {
[N in Extract<_ParseFieldsResultKeys<T>, AddressKeys>]: _GetParseFieldResultValue<T, N>;
}
} & {
[N in Exclude<_ParseFieldsResultKeys<T>, AddressKeys>]: _GetParseFieldResultValue<T, N>;
};

const parseFields = <T extends readonly FieldProps[]>(fields: T): ParseFieldsResult<T> => {

It's probably not a good idea to use this. But: Playground Link




Edit: realized we don't need the actual string literal types, with just string values we can get rid of _GetParseFieldResultValue and still get:


type Z = {
address: {
city: string;
state: string;
};
} & {
firstName: string;
lastName: string;
}

Playground Link


[#2707] Friday, September 4, 2020, 4 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
levidaylonj

Total Points: 392
Total Questions: 100
Total Answers: 112

Location: Bahrain
Member since Fri, Sep 16, 2022
2 Years ago
;