Vapor 4:如何将预加载的父级关联关系映射为自定义公开格式
方案1:全场景适用,无侵入原有模型逻辑
这个方案不需要修改原有模型的默认编码规则,兼容需要返回私有字段的内部接口场景,同时样板代码极少。
第一步先定义公开的User DTO和便捷转换方法:
// 仅包含User的公开字段 struct PublicUser: Content { let id: UUID? let name: String let avatar: String? } // 给User加快速转换方法,后续User字段变更只需要维护这一处 extension User { var publicInfo: PublicUser { .init( id: id, name: name, avatar: avatar ) } }
第二步如果不想完整重写Course的所有属性DTO,可以用@dynamicMemberLookup减少样板代码:
@dynamicMemberLookup struct PublicCourse: Content { private let base: Course let teacher: PublicUser init(_ course: Course) { self.base = course self.teacher = course.teacher.publicInfo } // 动态读取原有Course的属性,不需要重复定义 subscript<T>(dynamicMember keyPath: KeyPath<Course, T>) -> T { base[keyPath: keyPath] } // 仅需要在这里维护需要对外返回的字段 enum CodingKeys: String, CodingKey { case id, name, teacher } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(name, forKey: .name) try container.encode(teacher, forKey: .teacher) } }
最后修改路由的返回逻辑:
func list(req: Request) throws -> EventLoopFuture<[PublicCourse]> { return Course .query(on: req.db) .with(\.$teacher) .all() .map { $0.map(PublicCourse.init) } }
这种方案后续Course新增公开字段时,只需要在CodingKeys和encode方法各加一行即可,不需要重复定义属性,维护成本极低。
方案2:所有User返回场景都不需要私有字段时使用,代码量最少
如果你不存在需要返回somePrivateField的接口场景,可以直接重写User的编码规则,完全不需要额外的DTO:
extension User { // 仅列出需要对外返回的字段,私有字段直接排除 enum CodingKeys: String, CodingKey { case id, name, avatar } }
修改后直接保留原有Course查询逻辑即可,预加载teacher后自动不会输出私有字段:
func list(req: Request) throws -> EventLoopFuture<[Course]> { return Course .query(on: req.db) .with(\.$teacher) .all() }
高阶优化
如果项目模型多、场景复杂,可以使用Swift宏自动生成DTO和转换逻辑,完全消除样板代码,社区已有成熟的Vapor DTO生成宏可以直接使用。
内容的提问来源于stack exchange,提问作者Kevin Renskers
相关产品推荐
相关产品推荐

