name: "converting-ts-to-dart" description: "Converts TypeScript to idiomatic Dart 3 code, covering syntax mapping, type system translation, async patterns, and module migration. Activates when translating TypeScript interfaces, classes, enums, generics, union types, async/await patterns, or modules to Dart; migrating frontend or Node.js logic to Flutter/Dart; converting RxJS Observables to Dart Streams; adapting TypeScript utility types to Dart equivalents; or producing idiomatic Dart 3 output (sealed classes, records, pattern matching) from TypeScript source." metadata: last_modified: "2026-04-01 14:35:00 (GMT+8)"
TypeScript to Dart 3 Conversion Guide
Goal
Provide clear, idiomatic mappings from TypeScript syntax and patterns to modern Dart 3 equivalents. Every conversion should produce code that feels native to Dart — not a line-by-line transliteration.
Instructions
1. Type System Mapping
1.1 Primitive Types
TypeScript and Dart share similar primitives but with different names.
// TypeScript
let name: string = "Zack";
let age: number = 25;
let active: boolean = true;
let items: string[] = ["a", "b"];
let pair: [string, number] = ["age", 25];
// Dart 3
final name = 'Zack';
final age = 25;
final active = true;
final items = ['a', 'b'];
final pair = ('age', 25); // Record instead of Tuple
Key mappings:
| TypeScript | Dart |
|---|---|
string | String |
number | int / double / num |
boolean | bool |
any | dynamic (avoid when possible) |
unknown | Object? |
void | void |
null / undefined | null (Dart has no undefined) |
never | Never |
T[] / Array<T> | List<T> |
[A, B] (Tuple) | (A, B) (Record) |
Record<K, V> / { [key: string]: V } | Map<K, V> |
Set<T> | Set<T> |
Promise<T> | Future<T> |
Observable<T> | Stream<T> |
1.2 Union Types
Dart has no direct union type. Convert using sealed classes for complex cases, or Object + pattern matching for simple ones.
// TypeScript
type Result = Success | Failure;
type StringOrNum = string | number;
// Dart 3 — sealed class for ADTs
sealed class Result {}
class Success extends Result { final String data; Success(this.data); }
class Failure extends Result { final String error; Failure(this.error); }
// Simple union — use pattern matching
void process(Object value) {
switch (value) {
case String s: print('String: $s');
case int n: print('Int: $n');
}
}
1.3 Literal Types and Enums
// TypeScript
type Direction = "up" | "down" | "left" | "right";
enum Status { Active = "ACTIVE", Inactive = "INACTIVE" }
// Dart 3 — enhanced enum
enum Direction { up, down, left, right }
enum Status {
active('ACTIVE'),
inactive('INACTIVE');
final String value;
const Status(this.value);
}
2. Interface and Type Conversion
2.1 Interface → Class
Dart has no interface keyword. Every class implicitly defines an interface.
// TypeScript
interface User {
readonly id: string;
name: string;
email?: string;
greet(): string;
}
// Dart 3 — abstract interface class
abstract interface class User {
String get id;
String get name;
set name(String value);
String? get email;
String greet();
}
// Or as a concrete data class (more common)
class User {
final String id;
String name;
final String? email;
User({required this.id, required this.name, this.email});
String greet() => 'Hello, $name';
}
2.2 Type Alias → typedef / Record
// TypeScript
type Point = { x: number; y: number };
type Callback = (data: string) => void;
// Dart 3
typedef Point = ({double x, double y});
typedef Callback = void Function(String data);
2.3 Generic Constraints
// TypeScript
function merge<T extends object>(a: T, b: Partial<T>): T { ... }
// Dart 3
T merge<T extends Object>(T a, T b) { ... }
3. Class Features
3.1 Constructor Shorthand
Dart constructors are more concise than TypeScript's.
// TypeScript
class User {
readonly id: string;
name: string;
constructor(id: string, name: string) {
this.id = id;
this.name = name;
}
}
// Dart 3 — initializing formals
class User {
final String id;
String name;
User(this.id, this.name);
}
3.2 Access Modifiers
TypeScript uses private/protected/public. Dart uses _ prefix for library-private.
| TypeScript | Dart |
|---|---|
public | (default, no prefix) |
private | _ prefix |
protected | _ prefix (no true protected) |
readonly | final |
static | static |
abstract | abstract |
3.3 Inheritance Patterns
// TypeScript
abstract class Shape {
abstract area(): number;
}
class Circle extends Shape {
constructor(private radius: number) { super(); }
area(): number { return Math.PI * this.radius ** 2; }
}
// Dart 3
abstract class Shape {
double area();
}
class Circle extends Shape {
final double radius;
Circle(this.radius);
@override
double area() => pi * radius * radius; // import 'dart:math';
}
4. Functions and Async
4.1 Function Syntax
// TypeScript
const greet = (name: string, age?: number): string => {
return age != null ? `${name} is ${age}` : name;
};
// Dart 3
String greet(String name, [int? age]) =>
age != null ? '$name is $age' : name;
4.2 Named vs Positional Parameters
TypeScript object destructuring → Dart named parameters.
// TypeScript
function createUser({ name, age = 18 }: { name: string; age?: number }) { ... }
createUser({ name: "Zack" });
// Dart 3
void createUser({required String name, int age = 18}) { ... }
createUser(name: 'Zack');
4.3 Async / Await
Nearly identical, but Promise → Future, Promise.all → Future.wait.
// TypeScript
async function fetchUsers(): Promise<User[]> {
const res = await fetch("/api/users");
return res.json();
}
// Dart 3
Future<List<User>> fetchUsers() async {
final res = await http.get(Uri.parse('/api/users'));
return (jsonDecode(res.body) as List).map(User.fromJson).toList();
}
4.4 Stream (Observable equivalent)
// TypeScript (RxJS)
observable$.pipe(map(x => x * 2), filter(x => x > 5)).subscribe(console.log);
// Dart 3
stream.map((x) => x * 2).where((x) => x > 5).listen(print);
5. Null Safety
Both languages support strict null checking, with similar operators.
| TypeScript | Dart | Purpose |
|---|---|---|
x?.prop | x?.prop | Optional chaining |
x ?? y | x ?? y | Null coalescing |
x! | x! | Non-null assertion |
x?.prop ?? default | x?.prop ?? default | Chaining + fallback |
x ??= y | x ??= y | Null-aware assignment |
Key difference: Dart's null safety is sound — the compiler guarantees a non-nullable variable is never null at runtime. Prefer type promotion (if (x != null)) over the bang operator !.
6. Collections and Iteration
6.1 Array → List
// TypeScript
const nums = [1, 2, 3];
const doubled = nums.map(n => n * 2);
const evens = nums.filter(n => n % 2 === 0);
const sum = nums.reduce((a, b) => a + b, 0);
const hasNeg = nums.some(n => n < 0);
// Dart 3
final nums = [1, 2, 3];
final doubled = nums.map((n) => n * 2).toList();
final evens = nums.where((n) => n % 2 == 0).toList();
final sum = nums.fold(0, (a, b) => a + b);
final hasNeg = nums.any((n) => n < 0);
Method mapping:
| TypeScript | Dart |
|---|---|
.map() | .map().toList() |
.filter() | .where().toList() |
.reduce() | .fold() (with initial value) / .reduce() |
.some() | .any() |
.every() | .every() |
.find() | .firstWhere() |
.findIndex() | .indexWhere() |
.includes() | .contains() |
.flat() | .expand((e) => e).toList() |
.push() | .add() |
.splice() | .removeRange() / .insertAll() |
[...a, ...b] | [...a, ...b] |
6.2 Object → Map
// TypeScript
const config: Record<string, number> = { timeout: 30, retries: 3 };
Object.entries(config).forEach(([k, v]) => console.log(k, v));
// Dart 3
final config = <String, int>{'timeout': 30, 'retries': 3};
config.forEach((k, v) => print('$k $v'));
7. Pattern Matching (Dart 3 Exclusive Power)
Dart 3 pattern matching is more powerful than TypeScript's type narrowing. Lean into it during conversion.
// TypeScript — type narrowing
function describe(shape: Shape) {
if (shape instanceof Circle) {
return `Circle r=${shape.radius}`;
} else if (shape instanceof Rect) {
return `Rect ${shape.w}x${shape.h}`;
}
return "Unknown";
}
// Dart 3 — exhaustive switch expression
String describe(Shape shape) => switch (shape) {
Circle(:final radius) => 'Circle r=$radius',
Rect(:final w, :final h) => 'Rect ${w}x$h',
};
8. Module System
| TypeScript | Dart |
|---|---|
import { X } from './x' | import 'x.dart' (imports all public) |
import * as lib from './x' | import 'x.dart' as lib |
import { X as Y } from './x' | import 'x.dart' show X (rename not needed often) |
export { X } | Top-level public declarations are auto-exported |
export default X | No equivalent; just import the library |
import type { X } | Not needed; Dart imports are already type-aware |
9. Error Handling
// TypeScript
try {
await riskyOp();
} catch (e: unknown) {
if (e instanceof HttpError) { ... }
throw e;
}
// Dart 3
try {
await riskyOp();
} on HttpError catch (e) {
// handle
} catch (e, stackTrace) {
rethrow;
}
10. Common Patterns Quick Reference
| TypeScript Pattern | Dart 3 Equivalent |
|---|---|
JSON.stringify(obj) | jsonEncode(obj) |
JSON.parse(str) | jsonDecode(str) |
console.log(x) | print(x) / debugPrint(x) |
typeof x === 'string' | x is String |
x instanceof Foo | x is Foo |
setTimeout(fn, ms) | Future.delayed(Duration(milliseconds: ms), fn) |
setInterval(fn, ms) | Timer.periodic(Duration(milliseconds: ms), (_) => fn()) |
Date.now() | DateTime.now() |
Math.max(a, b) | max(a, b) (from dart:math) |
Object.keys(obj) | map.keys.toList() |
Array.from({length: n}, (_, i) => i) | List.generate(n, (i) => i) |
str.startsWith('x') | str.startsWith('x') |
str.split(',') | str.split(',') |
`Hello ${name}` | 'Hello $name' |
Constraints
- Always produce idiomatic Dart 3 — never write Java-style or TypeScript-style Dart.
- Prefer
finalovervarwhen the variable is not reassigned. - Use Records for lightweight multi-value returns; avoid creating throwaway classes.
- Use
sealedclasses for union type / ADT conversions — never fall back to rawObject+ casting when the domain is bounded. - Use
switchexpressions with destructuring overif-elsechains for type narrowing. - Preserve the original code's intent and naming conventions, adapting to Dart's
lowerCamelCase/UpperCamelCaserules. - Omit explicit type annotations where Dart's type inference handles it.