Skip to content
This repository was archived by the owner on Dec 4, 2025. It is now read-only.

Commit c62f935

Browse files
authored
Merge pull request #870 from linwumingshi/feature/map-key-enum
feat: ✨ Add support for Map with enum type keys
2 parents 24e0553 + 7aca4e5 commit c62f935

6 files changed

Lines changed: 155 additions & 56 deletions

File tree

src/main/java/com/ly/doc/builder/openapi/AbstractOpenApiBuilder.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public Map<String, Object> buildPaths(ApiConfig apiConfig, ApiSchema<ApiDoc> api
127127
String[] paths = methodDoc.getPath().split(";");
128128
for (String path : paths) {
129129
path = path.trim();
130-
Map<String, Object> request = buildPathUrls(apiConfig, methodDoc, methodDoc.getClazzDoc(),
130+
Map<String, Object> request = this.buildPathUrls(apiConfig, methodDoc, methodDoc.getClazzDoc(),
131131
apiSchema.getApiExceptionStatuses());
132132
if (!pathMap.containsKey(path)) {
133133
pathMap.put(path, request);
@@ -449,8 +449,11 @@ public Map<String, Object> buildProperties(List<ApiParam> apiParam, Map<String,
449449
}
450450

451451
/**
452-
* component schema properties data
453-
* @param apiParam ApiParam
452+
* component schema properties
453+
* @param apiParam list of ApiParam
454+
* @param component component
455+
* @param isResp is response
456+
* @return properties
454457
*/
455458
private Map<String, Object> buildPropertiesData(ApiParam apiParam, Map<String, Object> component, boolean isResp) {
456459
Map<String, Object> propertiesData = new HashMap<>();
@@ -540,12 +543,12 @@ public Map<String, Object> buildComponentData(ApiSchema<ApiDoc> apiSchema) {
540543
// request components
541544
String requestSchema = OpenApiSchemaUtil.getClassNameFromParams(method.getRequestParams());
542545
List<ApiParam> requestParams = method.getRequestParams();
543-
Map<String, Object> prop = buildProperties(requestParams, component, false);
546+
Map<String, Object> prop = this.buildProperties(requestParams, component, false);
544547
component.put(requestSchema, prop);
545548
// response components
546549
List<ApiParam> responseParams = method.getResponseParams();
547550
String responseSchemaName = OpenApiSchemaUtil.getClassNameFromParams(method.getResponseParams());
548-
component.put(responseSchemaName, buildProperties(responseParams, component, true));
551+
component.put(responseSchemaName, this.buildProperties(responseParams, component, true));
549552
});
550553
});
551554
// excption response components

src/main/java/com/ly/doc/builder/openapi/OpenApiBuilder.java

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,13 @@
2525
import com.ly.doc.constants.DocGlobalConstants;
2626
import com.ly.doc.constants.Methods;
2727
import com.ly.doc.constants.ParamTypeConstants;
28+
import com.ly.doc.helper.JavaProjectBuilderHelper;
2829
import com.ly.doc.model.*;
30+
import com.ly.doc.model.openapi.OpenApiTag;
31+
import com.ly.doc.utils.JsonUtil;
2932
import com.ly.doc.utils.OpenApiSchemaUtil;
3033
import com.power.common.util.CollectionUtil;
3134
import com.power.common.util.FileUtil;
32-
import com.ly.doc.helper.JavaProjectBuilderHelper;
33-
import com.ly.doc.model.openapi.OpenApiTag;
34-
import com.ly.doc.utils.JsonUtil;
35-
import com.power.common.util.StringUtil;
3635
import com.thoughtworks.qdox.JavaProjectBuilder;
3736
import org.apache.commons.lang3.StringUtils;
3837

@@ -70,11 +69,6 @@ public static void buildOpenApi(ApiConfig config, JavaProjectBuilder projectBuil
7069
INSTANCE.openApiCreate(config, apiSchema);
7170
}
7271

73-
/**
74-
* Build OpenApi
75-
* @param config Configuration of smart-doc
76-
* @param apiSchema List of API DOC
77-
*/
7872
@Override
7973
public void openApiCreate(ApiConfig config, ApiSchema<ApiDoc> apiSchema) {
8074
this.setComponentKey(getModuleName());
@@ -84,8 +78,8 @@ public void openApiCreate(ApiConfig config, ApiSchema<ApiDoc> apiSchema) {
8478
json.put("servers", buildServers(config));
8579
Set<OpenApiTag> tags = new HashSet<>();
8680
json.put("tags", tags);
87-
json.put("paths", buildPaths(config, apiSchema, tags));
88-
json.put("components", buildComponentsSchema(apiSchema));
81+
json.put("paths", this.buildPaths(config, apiSchema, tags));
82+
json.put("components", this.buildComponentsSchema(apiSchema));
8983

9084
String filePath = config.getOutPath();
9185
filePath = filePath + DocGlobalConstants.OPEN_API_JSON;
@@ -96,6 +90,7 @@ public void openApiCreate(ApiConfig config, ApiSchema<ApiDoc> apiSchema) {
9690
/**
9791
* Build openapi info
9892
* @param apiConfig Configuration of smart-doc
93+
* @return Map
9994
*/
10095
private static Map<String, Object> buildInfo(ApiConfig apiConfig) {
10196
Map<String, Object> infoMap = new HashMap<>(8);
@@ -107,6 +102,7 @@ private static Map<String, Object> buildInfo(ApiConfig apiConfig) {
107102
/**
108103
* Build Servers
109104
* @param config Configuration of smart-doc
105+
* @return List of Map
110106
*/
111107
private static List<Map<String, Object>> buildServers(ApiConfig config) {
112108
List<Map<String, Object>> serverList = new ArrayList<>();
@@ -116,12 +112,6 @@ private static List<Map<String, Object>> buildServers(ApiConfig config) {
116112
return serverList;
117113
}
118114

119-
/**
120-
* Build request
121-
* @param apiConfig Configuration of smart-doc
122-
* @param apiMethodDoc ApiMethodDoc
123-
* @param apiDoc apiDoc
124-
*/
125115
@Override
126116
public Map<String, Object> buildPathUrlsRequest(ApiConfig apiConfig, ApiMethodDoc apiMethodDoc, ApiDoc apiDoc,
127117
List<ApiExceptionStatus> apiExceptionStatuses) {
@@ -139,24 +129,26 @@ public Map<String, Object> buildPathUrlsRequest(ApiConfig apiConfig, ApiMethodDo
139129
// request.put("tags", new String[]{tag});
140130
// }
141131
request.put("tags", apiMethodDoc.getTagRefs().stream().map(TagDoc::getTag).toArray());
142-
request.put("requestBody", buildRequestBody(apiConfig, apiMethodDoc));
143-
request.put("parameters", buildParameters(apiMethodDoc));
144-
request.put("responses", buildResponses(apiConfig, apiMethodDoc, apiExceptionStatuses));
132+
request.put("requestBody", this.buildRequestBody(apiConfig, apiMethodDoc));
133+
request.put("parameters", this.buildParameters(apiMethodDoc));
134+
request.put("responses", this.buildResponses(apiConfig, apiMethodDoc, apiExceptionStatuses));
145135
request.put("deprecated", apiMethodDoc.isDeprecated());
146136
List<String> paths = OpenApiSchemaUtil.getPatternResult("[A-Za-z0-9_{}]*", apiMethodDoc.getPath());
147137
paths.add(apiMethodDoc.getType());
148138
String operationId = paths.stream().filter(StringUtils::isNotEmpty).collect(Collectors.joining("-"));
149139
request.put("operationId", operationId);
150140
// add extension attribution
151141
if (apiMethodDoc.getExtensions() != null) {
152-
apiMethodDoc.getExtensions().entrySet().forEach(e -> request.put("x-" + e.getKey(), e.getValue()));
142+
apiMethodDoc.getExtensions().forEach((key, value) -> request.put("x-" + key, value));
153143
}
154144
return request;
155145
}
156146

157147
/**
158-
* Build request body
148+
* Build requestBody
149+
* @param apiConfig Configuration of smart-doc
159150
* @param apiMethodDoc ApiMethodDoc
151+
* @return requestBody Map
160152
*/
161153
private Map<String, Object> buildRequestBody(ApiConfig apiConfig, ApiMethodDoc apiMethodDoc) {
162154
Map<String, Object> requestBody = new HashMap<>(8);
@@ -165,22 +157,17 @@ private Map<String, Object> buildRequestBody(ApiConfig apiConfig, ApiMethodDoc a
165157
|| apiMethodDoc.getType().equals(Methods.PATCH.getValue()));
166158
// add content of post method
167159
if (isPost) {
168-
requestBody.put("content", buildContent(apiConfig, apiMethodDoc, false));
160+
requestBody.put("content", this.buildContent(apiConfig, apiMethodDoc, false));
169161
return requestBody;
170162
}
171163
return null;
172164
}
173165

174-
/**
175-
* response body
176-
* @param apiMethodDoc ApiMethodDoc
177-
* @return response body
178-
*/
179166
@Override
180167
public Map<String, Object> buildResponsesBody(ApiConfig apiConfig, ApiMethodDoc apiMethodDoc) {
181168
Map<String, Object> responseBody = new HashMap<>(10);
182169
responseBody.put("description", "OK");
183-
responseBody.put("content", buildContent(apiConfig, apiMethodDoc, true));
170+
responseBody.put("content", this.buildContent(apiConfig, apiMethodDoc, true));
184171
return responseBody;
185172
}
186173

@@ -266,7 +253,7 @@ else if (desc.contains("string")) {
266253
parameters.putAll(buildParametersSchema(apiParam));
267254
}
268255
if (apiParam.getExtensions() != null && !apiParam.getExtensions().isEmpty()) {
269-
apiParam.getExtensions().entrySet().forEach(e -> parameters.put("x-" + e.getKey(), e.getValue()));
256+
apiParam.getExtensions().forEach((key, value) -> parameters.put("x-" + key, value));
270257
}
271258

272259
return parameters;
@@ -275,7 +262,7 @@ else if (desc.contains("string")) {
275262
@Override
276263
public Map<String, Object> buildComponentsSchema(ApiSchema<ApiDoc> apiSchema) {
277264
Map<String, Object> schemas = new HashMap<>(4);
278-
schemas.put("schemas", buildComponentData(apiSchema));
265+
schemas.put("schemas", this.buildComponentData(apiSchema));
279266
return schemas;
280267
}
281268

src/main/java/com/ly/doc/builder/openapi/SwaggerBuilder.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,14 @@
2424
import com.ly.doc.constants.DocGlobalConstants;
2525
import com.ly.doc.constants.MediaType;
2626
import com.ly.doc.constants.ParamTypeConstants;
27+
import com.ly.doc.helper.JavaProjectBuilderHelper;
2728
import com.ly.doc.model.*;
29+
import com.ly.doc.model.openapi.OpenApiTag;
2830
import com.ly.doc.utils.DocUtil;
29-
import com.ly.doc.utils.OpenApiSchemaUtil;
31+
import com.ly.doc.utils.JsonUtil;
3032
import com.power.common.util.CollectionUtil;
3133
import com.power.common.util.FileUtil;
3234
import com.power.common.util.StringUtil;
33-
import com.ly.doc.helper.JavaProjectBuilderHelper;
34-
import com.ly.doc.model.openapi.OpenApiTag;
35-
import com.ly.doc.utils.JsonUtil;
3635
import com.thoughtworks.qdox.JavaProjectBuilder;
3736
import org.apache.commons.lang3.StringUtils;
3837

@@ -138,7 +137,7 @@ public Map<String, Object> buildPathUrlsRequest(ApiConfig apiConfig, ApiMethodDo
138137
.stream()
139138
.filter(StringUtil::isNotEmpty)
140139
.toArray(n -> new String[n]));
141-
List<Map<String, Object>> parameters = buildParameters(apiMethodDoc);
140+
List<Map<String, Object>> parameters = this.buildParameters(apiMethodDoc);
142141
// requestBody
143142
if (CollectionUtil.isNotEmpty(apiMethodDoc.getRequestParams())) {
144143
Map<String, Object> parameter = new HashMap<>();

src/main/java/com/ly/doc/helper/JsonBuildHelper.java

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,12 @@ else if (JavaClassValidateUtil.isMap(typeName)) {
207207
data.append("{\"mapKey\":{}}");
208208
return data.toString();
209209
}
210-
if ((!JavaTypeConstants.JAVA_STRING_FULLY.equals(getKeyValType[0])) && apiConfig.isStrict()) {
211-
throw new RuntimeException("Map's key can only use String for json,but you use " + getKeyValType[0]);
210+
JavaClass mapKeyClass = builder.getJavaProjectBuilder().getClassByName(getKeyValType[0]);
211+
boolean mapKeyIsEnum = mapKeyClass.isEnum();
212+
if ((!JavaTypeConstants.JAVA_STRING_FULLY.equals(getKeyValType[0]) || !mapKeyIsEnum
213+
|| mapKeyClass.getEnumConstants().isEmpty()) && apiConfig.isStrict()) {
214+
throw new RuntimeException(
215+
"Map's key can only use String or Enum for json,but you use " + getKeyValType[0]);
212216
}
213217
String gicName = genericCanonicalName.substring(genericCanonicalName.indexOf(",") + 1,
214218
genericCanonicalName.lastIndexOf(">"));
@@ -228,11 +232,29 @@ else if (gicName.contains("<")) {
228232
data.append("{").append("\"mapKey\":").append(json).append("}");
229233
}
230234
else {
231-
data.append("{")
232-
.append("\"mapKey\":")
233-
.append(buildJson(gicName, genericCanonicalName, isResp, counter + 1, registryClasses, groupClasses,
234-
builder))
235-
.append("}");
235+
if (mapKeyIsEnum) {
236+
data.append("{");
237+
for (JavaField field : mapKeyClass.getFields()) {
238+
data.append("\"")
239+
.append(field.getName())
240+
.append("\":")
241+
.append(buildJson(gicName, genericCanonicalName, isResp, counter + 1, registryClasses,
242+
groupClasses, builder))
243+
.append(",");
244+
}
245+
// Remove the trailing comma
246+
if (data.charAt(data.length() - 1) == ',') {
247+
data.deleteCharAt(data.length() - 1);
248+
}
249+
data.append("}");
250+
}
251+
else {
252+
data.append("{")
253+
.append("\"mapKey\":")
254+
.append(buildJson(gicName, genericCanonicalName, isResp, counter + 1, registryClasses,
255+
groupClasses, builder))
256+
.append("}");
257+
}
236258
}
237259
return data.toString();
238260
}

src/main/java/com/ly/doc/helper/ParamsBuildHelper.java

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public static List<ApiParam> buildParams(String className, String pre, int level
110110
fieldNameConvert = PropertyNameHelper.translate(clsAnnotation);
111111
}
112112
JavaClassUtil.genericParamMap(genericMap, cls, globGicName);
113-
List<DocJavaField> fields = JavaClassUtil.getFields(cls, 0, new LinkedHashMap<>(), classLoader);
113+
114114
if (JavaClassValidateUtil.isPrimitive(simpleName)) {
115115
String processedType = processFieldTypeName(isShowJavaType, simpleName);
116116
paramList.addAll(primitiveReturnRespComment(processedType, atomicInteger, pid));
@@ -166,7 +166,7 @@ else if (JavaClassValidateUtil.isReactor(simpleName)) {
166166
}
167167
else {
168168
Map<String, String> ignoreFields = JavaClassUtil.getClassJsonIgnoreFields(cls);
169-
169+
List<DocJavaField> fields = JavaClassUtil.getFields(cls, 0, new LinkedHashMap<>(), classLoader);
170170
out: for (DocJavaField docField : fields) {
171171
JavaField field = docField.getJavaField();
172172
String maxLength = JavaFieldUtil.getParamMaxLength(field.getAnnotations());
@@ -649,6 +649,22 @@ else if (simpleName.equals(subTypeName)) {
649649
return paramList;
650650
}
651651

652+
/**
653+
* Builds a list of {@link ApiParam} objects for a map parameter.
654+
* @param globGicName the global generic name array
655+
* @param pre the prefix string
656+
* @param level the level of the parameter
657+
* @param isRequired the requirement status of the parameter
658+
* @param isResp the response flag
659+
* @param registryClasses the map of registry classes
660+
* @param projectBuilder the project configuration builder
661+
* @param groupClasses the set of group classes
662+
* @param pid the parent ID
663+
* @param jsonRequest the JSON request flag
664+
* @param nextLevel the next level of the parameter
665+
* @param atomicInteger the atomic integer for generating unique IDs
666+
* @return a list of {@link ApiParam} objects
667+
*/
652668
private static List<ApiParam> buildMapParam(String[] globGicName, String pre, int level, String isRequired,
653669
boolean isResp, Map<String, String> registryClasses, ProjectDocConfigBuilder projectBuilder,
654670
Set<String> groupClasses, int pid, boolean jsonRequest, int nextLevel, AtomicInteger atomicInteger) {
@@ -659,11 +675,13 @@ private static List<ApiParam> buildMapParam(String[] globGicName, String pre, in
659675
// mock map key param
660676
String mapKeySimpleName = DocClassUtil.getSimpleName(globGicName[0]);
661677
String valueSimpleName = DocClassUtil.getSimpleName(globGicName[1]);
678+
// get map key class
679+
JavaClass mapKeyClass = projectBuilder.getJavaProjectBuilder().getClassByName(mapKeySimpleName);
662680

681+
boolean isShowJavaType = projectBuilder.getApiConfig().getShowJavaType();
682+
String valueSimpleNameType = processFieldTypeName(isShowJavaType, valueSimpleName);
663683
List<ApiParam> paramList = new ArrayList<>();
664684
if (JavaClassValidateUtil.isPrimitive(mapKeySimpleName)) {
665-
boolean isShowJavaType = projectBuilder.getApiConfig().getShowJavaType();
666-
String valueSimpleNameType = processFieldTypeName(isShowJavaType, valueSimpleName);
667685
ApiParam apiParam = ApiParam.of()
668686
.setField(pre + "mapKey")
669687
.setType(valueSimpleNameType)
@@ -676,18 +694,66 @@ private static List<ApiParam> buildMapParam(String[] globGicName, String pre, in
676694
.setId(atomicOrDefault(atomicInteger, ++pid));
677695
paramList.add(apiParam);
678696
}
697+
else if (Objects.nonNull(mapKeyClass) && mapKeyClass.isEnum() && !mapKeyClass.getEnumConstants().isEmpty()) {
698+
Integer keyParentId = null;
699+
for (JavaField enumConstant : mapKeyClass.getEnumConstants()) {
700+
ApiParam apiParam = ApiParam.of()
701+
.setField(pre + enumConstant.getName())
702+
.setType(valueSimpleNameType)
703+
.setClassName(valueSimpleName)
704+
.setDesc(Optional.ofNullable(projectBuilder.getClassByName(valueSimpleName))
705+
.map(JavaClass::getComment)
706+
.orElse("A map key."))
707+
.setVersion(DocGlobalConstants.DEFAULT_VERSION)
708+
.setPid(null == keyParentId ? pid : keyParentId)
709+
.setId(paramList.size() + 1);
710+
if (null == keyParentId) {
711+
keyParentId = apiParam.getPid();
712+
}
713+
paramList.add(apiParam);
714+
List<ApiParam> apiParams = addValueParams(valueSimpleName, globGicName, level, isRequired, isResp,
715+
registryClasses, projectBuilder, groupClasses, apiParam.getId(), jsonRequest, nextLevel,
716+
atomicInteger);
717+
paramList.addAll(apiParams);
718+
}
719+
return paramList;
720+
}
721+
paramList.addAll(addValueParams(valueSimpleName, globGicName, level, isRequired, isResp, registryClasses,
722+
projectBuilder, groupClasses, pid, jsonRequest, nextLevel, atomicInteger));
723+
return paramList;
724+
}
725+
726+
/**
727+
* Adds parameters for the map value to the parameter list.
728+
* @param valueSimpleName the simple name of the value type
729+
* @param globGicName the global generic name array
730+
* @param level the level of the parameter
731+
* @param isRequired the requirement status of the parameter
732+
* @param isResp the response flag
733+
* @param registryClasses the map of registry classes
734+
* @param projectBuilder the project configuration builder
735+
* @param groupClasses the set of group classes
736+
* @param pid the parent ID
737+
* @param jsonRequest the JSON request flag
738+
* @param nextLevel the next level of the parameter
739+
* @param atomicInteger the atomic integer for generating unique IDs
740+
* @return the list of {@link ApiParam} objects
741+
*/
742+
private static List<ApiParam> addValueParams(String valueSimpleName, String[] globGicName, int level,
743+
String isRequired, boolean isResp, Map<String, String> registryClasses,
744+
ProjectDocConfigBuilder projectBuilder, Set<String> groupClasses, int pid, boolean jsonRequest,
745+
int nextLevel, AtomicInteger atomicInteger) {
679746
// build param when map value is not primitive
680747
if (JavaClassValidateUtil.isPrimitive(valueSimpleName)) {
681-
return paramList;
748+
return Collections.emptyList();
682749
}
683750
StringBuilder preBuilder = new StringBuilder();
684751
for (int j = 0; j < level; j++) {
685752
preBuilder.append(DocGlobalConstants.FIELD_SPACE);
686753
}
687754
preBuilder.append(DocGlobalConstants.PARAM_PREFIX);
688-
paramList.addAll(buildParams(globGicName[1], preBuilder.toString(), ++nextLevel, isRequired, isResp,
689-
registryClasses, projectBuilder, groupClasses, pid, jsonRequest, atomicInteger));
690-
return paramList;
755+
return buildParams(globGicName[1], preBuilder.toString(), ++nextLevel, isRequired, isResp, registryClasses,
756+
projectBuilder, groupClasses, pid, jsonRequest, atomicInteger);
691757
}
692758

693759
public static String dictionaryListComment(List<EnumDictionary> enumDataDict) {

0 commit comments

Comments
 (0)