SpringDoc/Swagger继承场景下必填属性的正确配置问询
解决SpringDoc中DTO子类必填属性在OpenAPI中位置错误的问题
问题原因
SpringDoc默认会将父类和子类的@NotNull注解对应的必填属性,合并到OpenAPI Schema的根节点required数组中,而非子类对应的allOf子对象内,导致TypeScript客户端生成工具(如orval)无法正确识别子类的必填属性。
解决方案
1. 为子类属性显式添加@Schema(requiredMode = REQUIRED)
直接在子类的必填属性上添加该注解,强制SpringDoc将该属性的必填标记绑定到子类对应的Schema中:
public class ParentDto { @NotNull private String parentProp; // getter/setter } public class ChildADto extends ParentDto { @NotNull @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private String childAProp; // getter/setter }
这种方式简单直接,适合单个子类或少量属性的场景。
2. 全局配置自定义ModelConverter
如果有大量子类需要处理,可以实现ModelConverter来调整继承关系下的必填属性位置:
import io.swagger.v3.core.converter.ModelConverter; import io.swagger.v3.core.converter.ModelConverterContext; import io.swagger.v3.oas.models.media.Schema; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; @Component public class InheritanceRequiredFixer implements ModelConverter { @Override public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) { Schema schema = chain.next().resolve(type, context, chain); if (schema != null && schema.getAllOf() != null && !schema.getAllOf().isEmpty()) { // 提取根节点的required属性,清空根节点的required List<String> rootRequired = new ArrayList<>(schema.getRequired() != null ? schema.getRequired() : Collections.emptyList()); schema.setRequired(new ArrayList<>()); // 将父类的必填属性绑定到第一个allOf节点(对应父类Schema) if (!rootRequired.isEmpty()) { Schema parentSchema = schema.getAllOf().get(0); parentSchema.setRequired(rootRequired); } } return schema; } }
这个转换器会自动将根节点的required属性移到父类对应的allOf子节点中,子类的必填属性则会保留在自身的Schema片段里。
3. 改用OpenAPI的allOf注解声明继承(替代Java继承)
如果允许调整DTO的结构,可以不用Java的extends,而是用@Schema(allOf = ParentDto.class)来声明继承关系,这样子类的属性和必填项会被单独放在对应的Schema中:
public class ParentDto { @NotNull private String parentProp; // getter/setter } @Schema(allOf = ParentDto.class) public class ChildADto { @NotNull private String childAProp; // getter/setter }
这种方式会让OpenAPI的结构更清晰,子类的required属性不会和父类的合并到根节点,但需要手动处理父类属性的传递(比如通过构造器或工具类复制)。
验证
调整后重新生成OpenAPI JSON,检查子类的必填属性是否出现在allOf对应的子对象的required数组中,而非根节点。此时用orval生成的TypeScript客户端就能正确识别子类的必填属性。
内容的提问来源于stack exchange,提问作者Xavier Portebois
相关产品推荐
相关产品推荐

