Object Chain into Array
TypeScript Model
- Class
- Interface
- Enum
// `MyPreference` Class
export class MyPreference {
preferences!: Subscription;
}
// `Subscription` class
export class Subscription {
consent!: boolean;
email!: string;
daily_alert_status!: Status;
special_alert_status!: Status;
daily!: {
dual: SubscriptionList;
high_priority: SubscriptionList;
low_priority: SubscriptionList;
};
special?: {
dual: SubscriptionList;
high_priority: SubscriptionList;
low_priority: SubscriptionList;
};
}
// `SubscriptionList` interface
export interface SubscriptionList {
filter: string;
query: string;
type: string;
status: boolean;
}
// `ProcessedList` interface
export interface ProcessedList extends SubscriptionList {
category: string;
subscription: string;
unique: string;
index: number;
}
// `Status` enum
export enum Status {
OptedIn = 'OptedIn',
OptedOut = 'OptedOut',
ConsentNotGivenYet = 'ConsentNotGivenYet',
}
const pref: Subscription = {
consent: false,
email: 'rajesh@ar.com',
daily_alert_status: Status.ConsentNotGivenYet,
special_alert_status: Status.OptedIn,
daily: {
dual: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false },
high_priority: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: true },
low_priority: { filter: 'utm=facebook', query: '', type: 'standard', status: false },
},
special: {
dual: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: true },
high_priority: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false },
low_priority: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false },
},
};
- Code
- Simplified Code
- using map
- Result
- SubscriptionList Data
/*
1. "SubObj" is a type alias for an object type where the keys are 'dual', 'high_priority',
and 'low_priority', and the values are of type "SubscriptionList".
2. Record<'dual' | 'high_priority' | 'low_priority', SubscriptionList>
is a utility type that creates an object type with the specified keys and values.
*/
type SubObj = Record<'dual' | 'high_priority' | 'low_priority', SubscriptionList>;
type SubObjKeys = keyof SubObj; // 'dual' | 'high_priority' | 'low_priority'
// SubObj and { [key: string]: SubscriptionList } are kind of same data type.
const arrayVal: [string, { [key: string]: SubscriptionList }][] = Object.entries(pref).filter((val) => {
return val[0] === 'daily' || val[0] === 'special';
});
/*
1. Extract the daily and special properties from the pref object.
2. Create an array with the required structure.
*/
const arrayVal: [
string,
{ dual: SubscriptionList; high_priority: SubscriptionList; low_priority: SubscriptionList }
][] = [
['daily', pref.daily],
['special', pref.special!]
];
// Create a new Map to store the relevant properties
const resultMap = new Map<
string,
{ dual: SubscriptionList; high_priority: SubscriptionList; low_priority: SubscriptionList }
>();
// Dynamically add the relevant properties to the map
Object.entries(pref).forEach(([key, value]) => {
if (typeof value === 'object' && value !== null) {
resultMap.set(
key,
value as { dual: SubscriptionList; high_priority: SubscriptionList; low_priority: SubscriptionList }
);
}
});
// Convert the Map to the desired array format
const arrayVal: [
string,
{ dual: SubscriptionList; high_priority: SubscriptionList; low_priority: SubscriptionList }
][] = Array.from(resultMap.entries());
/*
The resultMap will be as below:
Map {
'daily' => {
dual: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false },
high_priority: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: true },
low_priority: { filter: 'utm=facebook', query: '', type: 'standard', status: false },
},
'special' => {
dual: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: true },
high_priority: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false },
low_priority: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false },
}
}
*/
[
[
'daily',
{
dual: SubscriptionList;
high_priority: SubscriptionList;
low_priority: SubscriptionList;
}
],
[
'special',
{
dual: SubscriptionList;
high_priority: SubscriptionList;
low_priority: SubscriptionList;
}
]
]
{
filter: 'utm=facebook',
query: 'newsletter',
type: 'standard',
status: true
}
-
Object.entries(pref)will give['property','value'][]. See the below example.[['consent', false], ['email', 'rajesh@ar.com'], ...] -
Output format is :
[string, Object][].- Here
Objecttype isRecord<'dual' | 'high_priority' | 'low_priority', SubscriptionList>
- Here
The Record utility type in TypeScript is used to create an object type with a specific set of keys and a specific type for the values. In the context of Record<'dual' | 'high_priority' | 'low_priority', SubscriptionList>, it is used to define an object type where the keys are 'dual', 'high_priority', and 'low_priority', and the values are of type SubscriptionList.
Explanation
-
RecordUtility Type:- The
Record<K, T>utility type constructs an object type whose property keys areKand whose property values areT.
- The
-
Record<'dual' | 'high_priority' | 'low_priority', SubscriptionList>:- This creates an object type with keys
'dual','high_priority', and'low_priority'. - The values for these keys are of type
SubscriptionList.
- This creates an object type with keys
Let see the main logic loop:
- Code 1
- Code 2
- Final Result
let index = 0;
const result: ProcessedList[] = [];
arrayVal.forEach((val) => {
const category = val[0];
for (const key in val[1]) {
const unique = `${val[0]}_${key}`;
const obj: ProcessedList = { category, subscription: key, unique, index: index, ...pref[val[0]][key] };
result.push(obj);
index++;
}
});
/*
1. Iterate over the array of subscription objects.
2. For each subscription object, iterate over its properties (dual, high_priority, low_priority).
3. Create a new object for each property with the required structure.
4. Add these new objects to the result array.
*/
const result: ProcessedList[] = [];
subscriptions.forEach(([category, subscription], categoryIndex) => {
Object.entries(subscription).forEach(([subscriptionKey, subscriptionValue], subscriptionIndex) => {
result.push({
category,
subscription: subscriptionKey,
unique: `${category}_${subscriptionKey}`,
index: categoryIndex * 3 + subscriptionIndex,
...subscriptionValue
});
});
});
[
{
category: 'daily',
subscription: 'dual',
unique: 'daily_dual',
index: 0,
filter: 'utm=facebook',
query: 'newsletter',
type: 'standard',
status: false,
},
{
category: 'daily',
subscription: 'high_priority',
unique: 'daily_high_priority',
index: 1,
filter: 'utm=facebook',
query: 'newsletter',
type: 'standard',
status: true,
},
{
category: 'daily',
subscription: 'low_priority',
unique: 'daily_low_priority',
index: 2,
filter: 'utm=facebook',
query: '',
type: 'standard',
status: false,
},
{
category: 'special',
subscription: 'dual',
unique: 'special_dual',
index: 3,
filter: 'utm=facebook',
query: 'newsletter',
type: 'standard',
status: true,
},
{
category: 'special',
subscription: 'high_priority',
unique: 'special_high_priority',
index: 4,
filter: 'utm=facebook',
query: 'newsletter',
type: 'standard',
status: false,
},
{
category: 'special',
subscription: 'low_priority',
unique: 'special_low_priority',
index: 5,
filter: 'utm=facebook',
query: 'newsletter',
type: 'standard',
status: false,
},
];
Breakthrough of the above code
valwill iterate over array ofarrayVal. HerearrayValis like[[array1], [array2]].valis eitherarray1orarray2. And inner array has the type of[string, Object], here[string, Record<'dual' | 'high_priority' | 'low_priority', SubscriptionList>]- so
val[0]will be alwaysstring. And herestringwill be eitherdailyorspecial. - And
val[1]will be an object, here its type isRecord<'dual' | 'high_priority' | 'low_priority', SubscriptionList>. const key in val[1]will give the array which contains the keys of object. heredual,high_priorityandlow_priority.- So basically here,
val[0]is one of the keys of objectpref(its type isSubscription) andkeyis one of the keys of the object containing keysval[0](i.e, keys of the object eitherdailyorspecial). - So refereing both the keys. key of object and key of object's key (inner object) will give the output like this
{ filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: true } pref[val[0]][key]will be likepref['daily']['dual'](this is an example)- See the final output as below: The final output type is
Object[], hereProcessedList[]
Formatting JSON into desired array in single step
- Optimized Code 1
- Optimized Code 2
- Using map
- Explanation
// Convert the 'pref' object to the desired array format (Final Result tab)
/*
1. Iterate over the daily and special properties of the pref object.
2. For each property, iterate over its nested properties (dual, high_priority, low_priority).
3. Create a new object for each nested property with the required structure.
4. Add these new objects to the result array.
*/
const result: ProcessedList[] = [];
let index = 0;
['daily', 'special'].forEach(category => {
const categoryData = pref[category as keyof Subscription];
if (categoryData) {
Object.entries(categoryData).forEach(([subscription, details]) => {
result.push({
category,
subscription,
unique: `${category}_${subscription}`,
index: index++,
...details
});
});
}
});
console.log(result);
// To convert the pref object into the desired array format without explicitly mentioning ['daily', 'special'],
// you can dynamically iterate over the properties of the pref object.
const result: ProcessedList[] = [];
let index = 0;
Object.entries(pref).forEach(([category, categoryData]) => {
if (typeof categoryData === 'object' && categoryData !== null) {
Object.entries(categoryData).forEach(([subscription, details]) => {
result.push({
category,
subscription,
unique: `${category}_${subscription}`,
index: index++,
...details
});
});
}
});
console.log(result);
// Create a new Map to store the daily and special properties
const resultMap = new Map<string, { [key: string]: SubscriptionList }>();
// Dynamically add the daily and special properties to the map
Object.entries(pref).forEach(([key, value]) => {
if (typeof value === 'object' && value !== null) {
resultMap.set(key, value as { [key: string]: SubscriptionList });
}
});
// Convert the Map to the desired array format with the required structure
const resultArray: ProcessedList[] = [];
let index = 0;
resultMap.forEach((subscriptions, category) => {
Object.entries(subscriptions).forEach(([subscription, details]) => {
resultArray.push({
category,
subscription,
unique: `${category}_${subscription}`,
index: index++,
...details
});
});
});
console.log(resultArray);
Let's assume the category variable is set to 'daily':
const category = 'daily';
const categoryData: Subscription = pref[category as keyof Subscription];
console.log(categoryData);
The 'categoryData' will be assigned the value of the 'special' property of the 'pref' object:
So, the output of console.log(categoryData); will be:
{
dual: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: true },
high_priority: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false },
low_priority: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false },
}
The Object.entries(categoryData) will convert the above object into an array of key-value pairs as below:
[
['dual', { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: true }],
['high_priority', { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false }],
['low_priority', { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false }]
]
The Object.entries(pref) will convert 'pref' object into an array of key-value pairs as below:
[
['consent', false],
['email', 'rajesh@ar.com'],
['daily_alert_status', 'ConsentNotGivenYet'],
['special_alert_status', 'OptedIn'],
['daily', {
dual: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false },
high_priority: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: true },
low_priority: { filter: 'utm=facebook', query: '', type: 'standard', status: false },
}],
['special', {
dual: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: true },
high_priority: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false },
low_priority: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false },
}]
]
Case Study
The Record and Map types in TypeScript are both used to represent collections of key-value pairs, but they have different use cases and characteristics. Here is a comparison of Record and Map:
Record
- Definition:
Record<K, T>is a utility type that constructs an object type whose property keys areKand whose property values areT. - Use Case: Use
Recordwhen you need a simple object with a fixed set of keys and values of a specific type. - Syntax:
Record<'key1' | 'key2', ValueType> - Example:
interface SubscriptionList {
filter: string;
query: string;
type: string;
status: boolean;
}
type SubscriptionRecord = Record<'dual' | 'high_priority' | 'low_priority', SubscriptionList>;
const subscriptions: SubscriptionRecord = {
dual: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false },
high_priority: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: true },
low_priority: { filter: 'utm=facebook', query: '', type: 'standard', status: false },
};
Map
- Definition:
Mapis a built-in JavaScript object that allows you to store key-value pairs and provides methods to manipulate them. - Use Case: Use
Mapwhen you need a collection of key-value pairs with dynamic keys, or when you need methods to manipulate the collection (e.g.,set,get,has,delete). - Syntax:
Map<KeyType, ValueType> - Example:
interface SubscriptionList {
filter: string;
query: string;
type: string;
status: boolean;
}
const subscriptions = new Map<string, SubscriptionList>();
subscriptions.set('dual', { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false });
subscriptions.set('high_priority', { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: true });
subscriptions.set('low_priority', { filter: 'utm=facebook', query: '', type: 'standard', status: false });
console.log(subscriptions.get('dual'));
Key Differences
-
Fixed vs. Dynamic Keys:
Recordis used for objects with a fixed set of keys known at compile time.Mapis used for collections with dynamic keys that can be added or removed at runtime.
-
Methods:
Recordis a plain object and does not provide methods for manipulation.Mapprovides methods likeset,get,has,delete, andclear.
-
Iteration:
Recordcan be iterated usingObject.keys,Object.values, orObject.entries.Mapcan be iterated directly usingfor...ofloops or methods likeforEach.
Example Use Case
Here is an example of how you might use both Record and Map in a TypeScript project:
Using Record
interface SubscriptionList {
filter: string;
query: string;
type: string;
status: boolean;
}
type SubscriptionRecord = Record<'dual' | 'high_priority' | 'low_priority', SubscriptionList>;
const subscriptions: SubscriptionRecord = {
dual: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false },
high_priority: { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: true },
low_priority: { filter: 'utm=facebook', query: '', type: 'standard', status: false },
};
console.log(subscriptions.dual);
Using Map
interface SubscriptionList {
filter: string;
query: string;
type: string;
status: boolean;
}
const subscriptions = new Map<string, SubscriptionList>();
subscriptions.set('dual', { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: false });
subscriptions.set('high_priority', { filter: 'utm=facebook', query: 'newsletter', type: 'standard', status: true });
subscriptions.set('low_priority', { filter: 'utm=facebook', query: '', type: 'standard', status: false });
subscriptions.forEach((value, key) => {
console.log(`${key}: ${JSON.stringify(value)}`);
});
In summary, use Record for simple, fixed key-value pairs and Map for dynamic collections with more advanced manipulation needs.
Resources
- Object Chain into Array - GitHub
- Element implicitly has an 'any' type because expression of type 'string' can't be used to index type
- Element implicitly has an 'any' type because expression of type 'string' can't be used to index - stackoverflow
- How to dynamically assign properties to an object in TypeScript
- Typescript multiple object properties with shared types - stackoverflow
- Map - mdn