@fluojs/serialization
v1.0.0-beta.6
Published
Class-based response serialization and output shaping interceptors for Fluo.
Maintainers
Readme
@fluojs/serialization
Class-based response serialization and output shaping for fluo with decorator-aware recursive object walking.
Table of Contents
Installation
pnpm add @fluojs/serializationWhen to Use
- when you need output DTOs to expose only a controlled subset of fields
- when sensitive values such as password hashes or internal identifiers must never leave the response boundary
- when response data needs lightweight synchronous transforms during serialization
- when you want an HTTP interceptor to apply the same serialization rules automatically
Quick Start
import { Exclude, Expose, Transform, serialize } from '@fluojs/serialization';
class UserEntity {
@Expose()
id = '';
@Expose()
@Transform((value) => value.toUpperCase())
username = '';
@Exclude()
passwordHash = '';
}
const user = Object.assign(new UserEntity(), {
id: '1',
username: 'fluo',
passwordHash: 'secret',
});
console.log(serialize(user));
// { id: '1', username: 'FLUO' }Common Patterns
Expose-only output DTOs
import { Expose } from '@fluojs/serialization';
@Expose({ excludeExtraneous: true })
class SecureDto {
@Expose()
publicData = 'visible';
internalData = 'hidden';
}Value transforms
import { Transform } from '@fluojs/serialization';
class ProductDto {
@Transform((price) => `$${price.toFixed(2)}`)
price = 0;
}When the same field is decorated in a base class and a derived class, transforms run in declaration order from base to derived.
HTTP response shaping with an interceptor
import { Controller, Get, UseInterceptors } from '@fluojs/http';
import { SerializerInterceptor } from '@fluojs/serialization';
@Controller('/users')
@UseInterceptors(SerializerInterceptor)
class UsersController {
@Get('/')
findAll() {
return [new UserEntity()];
}
}SerializerInterceptor only serializes values that still belong to the normal HTTP response writer. If a handler or response helper commits RequestContext.response directly, such as an SSE stream, the interceptor returns that handler-owned value unchanged so the request pipeline preserves response ownership.
Cycle-safe serialization
The serializer cuts active cyclic references safely instead of recursing forever, so complex object graphs can still be turned into plain response-shaped objects without unbounded recursion. Completed shared references are reused in the serialized graph rather than dropped: if two sibling fields point at the same source object, both serialized fields point at the same serialized object. Only an object that is encountered again while it is already being serialized is cut to undefined.
Inherited decorator contracts
Serialization metadata declared on a base class is inherited by derived DTOs. @Expose(), @Exclude(), and @Transform() rules applied to shared base fields still take effect when you serialize subclass instances.
Class-level excludeExtraneous also follows normal inheritance. A derived class with @Expose() and no options keeps the nearest inherited setting, so an expose-only base DTO remains expose-only in subclasses. Use @Expose({ excludeExtraneous: false }) on the derived class only when you intentionally want to re-enable ordinary enumerable fields while still honoring inherited field-level @Exclude() metadata.
Undecorated class instances are still traversed recursively, so decorated nested descendants are respected even when the parent object has no serialization metadata.
Plain-object safety
serialize() treats plain objects and null-prototype records as data containers, not decorated class instances. Enumerable symbol keys are serialized, own __proto__, constructor, and prototype keys are treated as data rather than prototype mutations, and objects with custom or unsafe constructor fields are walked safely without throwing.
Non-JSON leaf values
serialize() applies decorator metadata and recursively walks arrays/plain objects, but it does not coerce every leaf into strict JSON types. Opaque built-ins such as Date, Map, Set, URL, URLSearchParams, RegExp, Error, ArrayBuffer, typed arrays, WeakMap, WeakSet, and Promise pass through unchanged instead of being flattened as DTO-like class instances. Values such as bigint, functions, and symbols can also pass through unchanged unless you normalize them with @Transform(...) or before writing the final HTTP response.
Public API Overview
- Decorators:
Expose,Exclude,Transform - Engine:
serialize(value) - HTTP integration:
SerializerInterceptor
Expose can be applied to classes and fields. Exclude and Transform apply to fields.
Related Packages
@fluojs/http: appliesSerializerInterceptorto HTTP handlers@fluojs/validation: handles input-side DTO materialization and validation
Example Sources
packages/serialization/src/serialize.test.tspackages/serialization/src/serializer-interceptor.test.ts
