Skip to main content

Object Chain into Array

TypeScript Model
// `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;
};
}
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 },
},
};
/*
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';
});
  • Object.entries(pref) will give ['property','value'][] . See the below example.

    [['consent', false], ['email', 'rajesh@ar.com'], ...]
  • Output format is : [string, Object][].

    • Here Object type is Record<'dual' | 'high_priority' | 'low_priority', SubscriptionList>

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

  1. Record Utility Type:

    • The Record<K, T> utility type constructs an object type whose property keys are K and whose property values are T.
  2. 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.

Let see the main logic loop:

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++;
}
});

Breakthrough of the above code

  1. val will iterate over array of arrayVal. Here arrayVal is like [[array1], [array2]]. val is either array1 or array2. And inner array has the type of [string, Object], here [string, Record<'dual' | 'high_priority' | 'low_priority', SubscriptionList>]
  2. so val[0] will be always string. And here string will be either daily or special.
  3. And val[1] will be an object, here its type is Record<'dual' | 'high_priority' | 'low_priority', SubscriptionList>.
  4. const key in val[1] will give the array which contains the keys of object. here dual, high_priority and low_priority.
  5. So basically here, val[0] is one of the keys of object pref (its type is Subscription) and key is one of the keys of the object containing keys val[0] (i.e, keys of the object either daily or special).
  6. 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 }
  7. pref[val[0]][key] will be like pref['daily']['dual'] (this is an example)
  8. See the final output as below: The final output type is Object[], here ProcessedList[]

Formatting JSON into desired array in single step

Optimized way & best practice
// 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);

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 are K and whose property values are T.
  • Use Case: Use Record when 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: Map is a built-in JavaScript object that allows you to store key-value pairs and provides methods to manipulate them.
  • Use Case: Use Map when 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

  1. Fixed vs. Dynamic Keys:

    • Record is used for objects with a fixed set of keys known at compile time.
    • Map is used for collections with dynamic keys that can be added or removed at runtime.
  2. Methods:

    • Record is a plain object and does not provide methods for manipulation.
    • Map provides methods like set, get, has, delete, and clear.
  3. Iteration:

    • Record can be iterated using Object.keys, Object.values, or Object.entries.
    • Map can be iterated directly using for...of loops or methods like forEach.

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

  1. Object Chain into Array - GitHub
  2. Element implicitly has an 'any' type because expression of type 'string' can't be used to index type
  3. Element implicitly has an 'any' type because expression of type 'string' can't be used to index - stackoverflow
  4. How to dynamically assign properties to an object in TypeScript
  5. Typescript multiple object properties with shared types - stackoverflow
  6. Map - mdn