A TypeScript entity framework with decorators for metadata-driven serialization and deserialization.
npm install @rtpaulino/entityA TypeScript entity framework with decorators for metadata-driven serialization and deserialization.
- Type-safe decorators for entity properties
- Serialization via EntityUtils.toJSON()
- Deserialization via EntityUtils.parse()
- Helper decorators for common types (String, Number, Boolean, Date, BigInt, Entity, Array)
- Nested entities with automatic recursive handling
- Optional properties with null/undefined support
- Sparse arrays for arrays with null/undefined elements
- Passthrough mode for generic types like Record
``bash`
npm install @rtpaulino/entity reflect-metadata
Make sure to enable decorators in your tsconfig.json:
`json`
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
`typescript
import 'reflect-metadata';
import {
Entity,
StringProperty,
NumberProperty,
EntityUtils,
} from '@rtpaulino/entity';
@Entity()
class User {
@StringProperty()
name!: string;
@NumberProperty()
age!: number;
}
// Serialization
const user = new User();
user.name = 'Alice';
user.age = 30;
const json = EntityUtils.toJSON(user);
// { name: 'Alice', age: 30 }
// Deserialization
const parsed = EntityUtils.parse(User, json);
// parsed is a User instance with name='Alice' and age=30
`
`typescript
@Entity()
class Example {
@StringProperty()
text!: string;
@NumberProperty()
count!: number;
@BooleanProperty()
active!: boolean;
@DateProperty()
createdAt!: Date;
@BigIntProperty()
largeNumber!: bigint;
}
`
Use optional: true to allow null or undefined:
`typescript
@Entity()
class User {
@StringProperty()
name!: string;
@StringProperty({ optional: true })
email?: string | null;
}
`
`typescript
@Entity()
class Address {
@StringProperty()
city!: string;
@StringProperty()
country!: string;
}
@Entity()
class User {
@StringProperty()
name!: string;
@EntityProperty(() => Address)
address!: Address;
}
`
`typescript
@Entity()
class Team {
@ArrayProperty(() => String)
members!: string[];
@ArrayProperty(() => Number)
scores!: number[];
}
`
Use sparse: true to allow null or undefined elements in arrays:
`typescript
@Entity()
class Data {
@ArrayProperty(() => String, { sparse: true })
values!: (string | null)[];
}
const data = new Data();
data.values = ['a', null, 'b', undefined, 'c'];
const json = EntityUtils.toJSON(data);
// { values: ['a', null, 'b', null, 'c'] }
const parsed = EntityUtils.parse(Data, json);
// parsed.values is ['a', null, 'b', null, 'c']
`
By default, the library throws errors for unknown types to prevent accidental misuse. Use @PassthroughProperty() decorator or passthrough: true option to explicitly handle generic types like Record, any, or custom objects:
`typescript
@Entity()
class Config {
@StringProperty()
name!: string;
@PassthroughProperty()
metadata!: Record
@PassthroughProperty()
customData!: any;
}
const config = new Config();
config.name = 'MyConfig';
config.metadata = {
version: '1.0.0',
tags: ['production', 'stable'],
nested: { deeply: { nested: { value: 42 } } },
};
// Serialization - data passes through as-is
const json = EntityUtils.toJSON(config);
// Deserialization - data passes through as-is
const parsed = EntityUtils.parse(Config, json);
// parsed.metadata is exactly what was in the JSON
`
Important: Passthrough cannot be combined with array, optional, or sparse options. Use @PassthroughProperty() for the cleanest API.
Without passthrough, unknown types throw errors:
`typescript
@Entity()
class BadExample {
@Property({ type: () => Symbol }) // Symbol is not supported
value!: symbol;
}
// This will throw:
// "Property 'value' has unknown type constructor. Supported types are: String, Number, Boolean, Date, BigInt, and @Entity() classes."
`
`typescript
@Entity()
class Comment {
@StringProperty()
text!: string;
@DateProperty()
createdAt!: Date;
}
@Entity()
class Post {
@StringProperty()
title!: string;
@ArrayProperty(() => Comment)
comments!: Comment[];
}
`
`typescript
@Entity()
class Organization {
@StringProperty()
name!: string;
@ArrayProperty(() => User)
admins!: User[];
@Property({ passthrough: true })
settings!: Record
}
`
Serializes an entity to a plain JavaScript object.
- Throws error for properties with unknown types (unless passthrough: true)
- Handles nested entities recursively
- Converts Date to ISO string, BigInt to string
- Preserves null/undefined for optional properties
Deserializes a plain object to an entity instance.
- Validates all required properties are present
- Validates property types match metadata
- Throws error for unknown types (unless passthrough: true)
- Handles nested entities recursively
- Converts ISO strings back to Date, strings back to BigInt
The @Property() decorator requires configuration options. Available options are:
- type: () => any - Required. Type constructor for the propertyarray?: boolean
- - Whether the property is an arrayoptional?: boolean
- - Allow null/undefined valuessparse?: boolean
- - Allow null/undefined in array elements (requires array: true)passthrough?: boolean
- - Bypass type validation for generic types (cannot be combined with array, optional, or sparse)equals?: (a, b) => boolean
- - Custom equality function for this property
Note: Use helper decorators like @StringProperty(), @PassthroughProperty(), etc. for cleaner, more readable code. These helpers automatically provide the required type parameter.
The library validates configuration at decorator time and throws descriptive errors for common mistakes:
`typescript
// Missing required property
EntityUtils.parse(User, {});
// Error: Property 'name' is required but missing from input
// Null/undefined on required property
EntityUtils.parse(User, { name: null, age: 30 });
// Error: Property 'name' cannot be null or undefined
// Sparse without array (fails at decorator time)
@Property({ type: () => String, sparse: true }) // Missing array: true
values!: string[];
// Error: Property 'values' has sparse: true but array is not true
// Passthrough combined with other options (fails at decorator time)
@Property({ type: () => Object, passthrough: true, array: true })
data!: any;
// Error: Property 'data' has passthrough: true and array: true. Passthrough cannot be combined with array
// Unknown type without passthrough
@Property({ type: () => WeakMap })
map!: WeakMap
// Error: Property 'map' has unknown type constructor
`
All methods are fully typed:
`typescript`
const user = EntityUtils.parse(User, json); // user is typed as User
const json = EntityUtils.toJSON(user); // json is Record
MIT
`
``