
本文介绍在 typeorm 中为实体高效存储嵌套对象数组(如图标列表)的两种主流方案:使用 jsonb 字段直接序列化,或通过一对多关系建模;重点演示 jsonb 方式,兼顾类型安全与开发简洁性。
在实际业务中,常需将结构化但非强关联的数据(如 icons 数组)作为字段嵌入主实体,而非单独建表。TypeORM 提供了灵活支持——推荐优先采用 jsonb 类型(PostgreSQL)或 json 类型(MySQL/SQLite)直接存储对象数组,既保持数据内聚性,又避免过度规范化带来的复杂 JOIN 查询。
✅ 推荐方案:使用 @Column({ type: 'jsonb' })(PostgreSQL)
// icons.interface.ts
export interface Icon {
icon: string;
main: boolean;
}
// category.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
// ✅ 存储 Icon[] 数组,自动序列化/反序列化
@Column({ type: 'jsonb', nullable: true, default: () => '[]' })
icons: Icon[];
}⚠️ 注意:default: [] 在 TypeScript 中会被忽略(因装饰器无法运行时计算),应改用 default: () => '[]' 确保数据库默认值生效。MySQL 用户请替换为 { type: 'json', default: () => '[]' }。
? 使用示例
// 创建带图标的数据
const category = new Category();
category.title = 'Title text';
category.icons = [
{ icon: 'icon-1.png', main: true },
{ icon: 'icon-2.png', main: false },
{ icon: 'icon-3.png', main: false }
];
await repository.save(category);查询时,icons 字段将自动解析为 Icon[] 类型数组,可直接遍历或解构:
const loaded = await repository.findOneBy({ id: 1 });
console.log(loaded.icons[0].icon); // "icon-1.png"? 替代方案:关系型建模(适用需独立查询/索引/约束场景)
若需对 icon 单独建立索引、关联其他表,或频繁按 main = true 查询,则建议拆分为独立实体:
@Entity()
export class Icon {
@PrimaryGeneratedColumn()
id: number;
@Column()
icon: string;
@Column({ default: false })
main: boolean;
@ManyToOne(() => Category, category => category.icons)
category: Category;
}
@Entity()
export class Category {
// ... 其他字段
@OneToMany(() => Icon, icon => icon.category, { cascade: true })
icons: Icon[];
}此时需手动管理关联,增删更复杂,但灵活性与数据库能力更强。
✅ 总结建议
- 轻量嵌套、读多写少、无复杂查询需求 → 选 jsonb/json 字段:开发快、结构清晰、TypeScript 类型友好;
- 需独立 CRUD、事务一致性、外键约束或高频条件筛选 → 选关系实体;
- 始终为 jsonb 字段添加 nullable: true 和显式 default,避免 NULL 值引发运行时错误;
- 在 NestJS 等框架中,可配合 ValidationPipe 对 icons 字段做 DTO 层校验,进一步保障数据质量。










