typescript
25 lines · 7 steps
Flattening nested objects into dotted keys
A recursive reducer collapses a nested object into a single-level map with dot-delimited paths.
Explained by
highlit
1type Flatten = Record<string, unknown>;
2
3function isPlainObject(value: unknown): value is Record<string, unknown> {
4 return (
5 typeof value === "object" &&
6 value !== null &&
7 !Array.isArray(value) &&
8 (Object.getPrototypeOf(value) === Object.prototype ||
9 Object.getPrototypeOf(value) === null)
10 );
11}
12
13export function flatten(source: Record<string, unknown>, prefix = ""): Flatten {
14 return Object.entries(source).reduce<Flatten>((acc, [key, value]) => {
15 const path = prefix ? `${prefix}.${key}` : key;
16
17 if (isPlainObject(value) && Object.keys(value).length > 0) {
18 Object.assign(acc, flatten(value, path));
19 } else {
20 acc[path] = value;
21 }
22
23 return acc;
24 }, {});
25}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A strict type guard prevents recursing into arrays or class instances you meant to keep whole.
- 2Recursion plus an accumulated prefix turns tree structure into flat, path-addressable keys.
- 3Treating empty objects as leaves avoids silently dropping keys that have no children.
Related explainers
typescript
import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, timer, throwError } from 'rxjs'; import { switchMap, takeWhile, filter, take, catchError } from 'rxjs/operators';
Polling a job until it finishes in Angular
rxjs
polling
observables
Intermediate
7 steps
typescript
import { Injectable, signal, computed, effect, inject } from '@angular/core'; import { DOCUMENT } from '@angular/common'; export type Theme = 'light' | 'dark';
A signal-based theme service in Angular
signals
reactivity
dependency-injection
Intermediate
7 steps
typescript
import { Injectable } from '@angular/core'; import { HttpClient, HttpEventType, HttpRequest } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map, distinctUntilChanged, scan } from 'rxjs/operators';
Tracking upload progress in Angular
rxjs
http-events
state-reduction
Intermediate
8 steps
typescript
import { Component, HostBinding, Input } from '@angular/core'; type ProgressVariant = 'success' | 'warning' | 'danger';
A CSS-driven progress ring in Angular
host-bindings
css-custom-properties
input-setters
Intermediate
8 steps
typescript
import { Controller, Param, Sse, MessageEvent } from '@nestjs/common'; import { Observable, interval, merge } from 'rxjs'; import { filter, map, takeWhile } from 'rxjs/operators'; import { JobService } from './job.service';
Streaming job progress with SSE in NestJS
server-sent-events
reactive-streams
rxjs
Intermediate
8 steps
typescript
import { Controller, All, Req,
Catch-all routes and error shaping in NestJS
exception-handling
routing
middleware
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/flattening-nested-objects-into-dotted-keys-explained-typescript-0b63/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.