Skip to content

ObjectBody

In jin-frame, when you declare a class field with the @ObjectBody() decorator, the entire object is serialized into the HTTP Request Body and sent as-is. This is useful when you want to handle a whole object at once rather than splitting it into multiple @Body() fields.

Quick Example

ts
interface IUser {
  username: string;
  age?: number;
  tags?: string[];
  isAdmin?: boolean;
}

@Post({ host: 'https://api.example.com', path: '/users' })
export class CreateUserFrame extends JinFrame {
  @ObjectBody() declare readonly user: IUser;
}

// Execute
const frame = CreateUserFrame.of({
  user: { username: 'pikachu', age: 7, tags: ['red', 'blue'], isAdmin: false },
});
const reply = await frame.execute();

console.log(reply.config.data);
// { "username": "pikachu", "age": 7, "tags": ["red","blue"], "isAdmin": false }

Difference: Body vs ObjectBody

Category@Body@ObjectBody
DeclarationDefine each field individuallyDefine the entire object as a single field
Usage@Body() declare readonly name: string;@ObjectBody() declare readonly user: User;
SerializationMultiple fields are merged into the bodyThe declared object is serialized as-is
Use CaseAPIs focusing on simple fieldsPassing a full DTO/interface in one shot

Nested Objects

@ObjectBody() also supports nested object structures and serializes them as-is.

ts
interface IProfile {
  name: string;
  age: number;
}

interface IUserSettings {
  theme: string;
  notifications: boolean;
}

interface IUser {
  profile: IProfile;
  settings?: IUserSettings;
}

@Post({ host: 'https://api.example.com', path: '/users' })
export class NestedUserFrame extends JinFrame {
  @ObjectBody() declare readonly user: IUser;
}

const reply = await NestedUserFrame.of({
  user: {
    profile: { name: 'pikachu', age: 7 },
    settings: { theme: 'dark', notifications: true },
  },
}).execute();

// Body → { "profile": { "name": "pikachu", "age": 7 }, "settings": { "theme": "dark", "notifications": true } }

Array Support

Objects containing arrays are serialized as-is.

ts
interface IBatch {
  ids: number[];
}

@Post({ host: 'https://api.example.com', path: '/batch' })
export class BatchFrame extends JinFrame {
  @ObjectBody() declare readonly data: IBatch;
}

const reply = await BatchFrame.of({ data: { ids: [1, 2, 3] } }).execute();
// Body → { "ids": [1,2,3] }

Optional Fields

If a value inside the object is undefined or null, it is automatically omitted.

ts
interface IUser {
  username: string;
  nickname?: string;
}

@Post({ host: 'https://api.example.com', path: '/users' })
export class OptionalUserFrame extends JinFrame {
  @ObjectBody() declare readonly user: IUser;
}

const reply = await OptionalUserFrame.of({ user: { username: 'pikachu' } }).execute();
// Body → { "username": "pikachu" }

Combining with Params & Headers

ts
interface IUser {
  username: string;
  age: number;
}

@Post({ host: 'https://api.example.com', path: '/orgs/:orgId/users' })
export class CreateUserInOrgFrame extends JinFrame {
  @Param() declare readonly orgId: string;
  @Header() declare readonly Authorization: string;
  @ObjectBody() declare readonly user: IUser;
}

const reply = await CreateUserInOrgFrame.of({
  orgId: 'acme',
  Authorization: 'Bearer token',
  user: { username: 'pikachu', age: 7 },
}).execute();

// POST /orgs/acme/users
// Headers: { Authorization: "Bearer token" }
// Body: { "username": "pikachu", "age": 7 }

Debugging Tip

Right before sending the request, you can check the final body data with frame.getData('body').

ts
const frame = CreateUserFrame.of({ user: { username: 'pikachu', age: 7 } });
const req = frame.request();

console.log(frame.getData('body')); // { username: 'pikachu', age: 7 }
console.log(req.data); // Final serialized body