Skip to content

Commit

Permalink
Merge pull request #924 from linwumingshi/fix/map-enum-key-primitive-…
Browse files Browse the repository at this point in the history
…value-json-example

fix:  🐛 Fix empty JSON example value for Map with enum keys and primitive values
  • Loading branch information
shalousun authored Oct 9, 2024
2 parents 6ea5e09 + 94bbe7d commit 8ed7b32
Show file tree
Hide file tree
Showing 14 changed files with 1,139 additions and 740 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ public interface DocAnnotationConstants {
*/
String VALUE_PROP = "value";

/**
* annotation using prop
*/
String USING_PROP = "using";

/**
* annotation path prop
*/
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/ly/doc/constants/DocGlobalConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -614,4 +614,16 @@ public interface DocGlobalConstants {
*/
String DEFAULT_MAP_KEY_DESC = "A map key.";

/**
* object map value warning.
*/
String OBJECT_MAP_VALUE_WARNING = "{\"warning\":\"Using java.util.Object as a Map value is not recommended. "
+ "Smart-doc cannot process it properly. Please use a specific type for better documentation generation.\"}";

/**
* object list warning.
*/
String GENERIC_LIST_WARNING = "{\"warning\":\"Using java.util.Object in a List instead of a specific generic type is not recommended. "
+ "Smart-doc cannot display the correct generics. Please specify the generic type for better documentation generation.\"}";

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
*/
package com.ly.doc.extension.json;

// @formatter:off
/**
* copy from jackson and singleton instances.
*
* @see <a href=
* "https://github.com/FasterXML/jackson-databind/blob/2.18/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategies.java">PropertyNamingStrategies</a>.
* <p>
* <a href="https://github.com/FasterXML/jackson-databind/blob/2.18/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategies.java">PropertyNamingStrategies</a>.
* @author xingzi
*/
// @formatter:on
public abstract class PropertyNamingStrategies implements java.io.Serializable {

/**
Expand Down
217 changes: 217 additions & 0 deletions src/main/java/com/ly/doc/helper/BaseHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,27 @@
*/
package com.ly.doc.helper;

import com.ly.doc.builder.ProjectDocConfigBuilder;
import com.ly.doc.constants.DocAnnotationConstants;
import com.ly.doc.constants.DocTags;
import com.ly.doc.constants.JSRAnnotationConstants;
import com.ly.doc.model.CustomField;
import com.ly.doc.model.CustomFieldInfo;
import com.ly.doc.model.DocJavaField;
import com.ly.doc.model.FieldJsonAnnotationInfo;
import com.ly.doc.utils.DocUtil;
import com.ly.doc.utils.JavaClassUtil;
import com.ly.doc.utils.JavaClassValidateUtil;
import com.power.common.util.CollectionUtil;
import com.power.common.util.StringEscapeUtil;
import com.power.common.util.StringUtil;
import com.thoughtworks.qdox.model.JavaAnnotation;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.expression.AnnotationValue;

import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
* Abstract Base helper
Expand Down Expand Up @@ -70,4 +84,207 @@ protected static String getFieldValueFromMock(Map<String, String> tagsMap) {
return fieldValue;
}

/**
* check custom field is ignored
* @param docField doc field
* @param isResp is resp
* @param customRequestField custom request field
* @param customResponseField custom response field
* @return boolean
*/
protected static boolean isIgnoreCustomField(DocJavaField docField, boolean isResp, CustomField customRequestField,
CustomField customResponseField) {
if (Objects.nonNull(customRequestField) && JavaClassUtil.isTargetChildClass(docField.getDeclaringClassName(),
customRequestField.getOwnerClassName()) && (customRequestField.isIgnore()) && !isResp) {
return true;
}
return Objects.nonNull(customResponseField) && JavaClassUtil
.isTargetChildClass(docField.getDeclaringClassName(), customResponseField.getOwnerClassName())
&& (customResponseField.isIgnore()) && isResp;
}

/**
* check field is transient
* @param field field
* @param projectBuilder project builder
* @param isResp is resp
* @return boolean
*/
protected static boolean isTransientField(JavaField field, ProjectDocConfigBuilder projectBuilder, boolean isResp) {
if (field.isTransient()) {
return (projectBuilder.getApiConfig().isSerializeRequestTransients() && !isResp)
|| (projectBuilder.getApiConfig().isSerializeResponseTransients() && isResp);
}
return false;
}

/**
* Get a field JSON annotation information for a given field.
* @param projectBuilder the project builder
* @param docField the doc of java field
* @param isResp the response flag for the parameter
* @param groupClasses the group classes
* @param methodJsonViewClasses the method JSON view classes
* @return the field JSON annotation information {@link FieldJsonAnnotationInfo}
*
*/
protected static FieldJsonAnnotationInfo getFieldJsonAnnotationInfo(ProjectDocConfigBuilder projectBuilder,
DocJavaField docField, boolean isResp, Set<String> groupClasses, Set<String> methodJsonViewClasses) {
FieldJsonAnnotationInfo fieldJsonAnnotationInfo = new FieldJsonAnnotationInfo();
// Handle @JsonView; if the field is not annotated with @JsonView, skip
if (!methodJsonViewClasses.isEmpty() && isResp && docField.getAnnotations().isEmpty()) {
return fieldJsonAnnotationInfo;
}

for (JavaAnnotation annotation : docField.getAnnotations()) {
// if the field is annotated with @JsonIgnore || @JsonProperty, then
// check if it belongs to the groupClasses
if (JavaClassValidateUtil.isIgnoreFieldJson(annotation, isResp)) {
fieldJsonAnnotationInfo.setIgnore(true);
continue;
}
// Handle @JsonView
if (JavaClassUtil.shouldExcludeFieldFromJsonView(annotation, methodJsonViewClasses, isResp,
projectBuilder)) {
fieldJsonAnnotationInfo.setIgnore(true);
return fieldJsonAnnotationInfo;
}

String annotationName = annotation.getType().getValue();
// if the field is annotated with @JsonSerialize
if (DocAnnotationConstants.SHORT_JSON_SERIALIZE.equals(annotationName)
&& DocAnnotationConstants.TO_STRING_SERIALIZER_USING
.equals(annotation.getNamedParameter(DocAnnotationConstants.USING_PROP))) {
fieldJsonAnnotationInfo.setToStringSerializer(true);
continue;
}
// if the field is annotated with @Null And isResp is false
if (JSRAnnotationConstants.NULL.equals(annotationName) && !isResp) {
if (CollectionUtil.isEmpty(groupClasses)) {
fieldJsonAnnotationInfo.setIgnore(true);
return fieldJsonAnnotationInfo;
}
Set<String> groupClassList = JavaClassUtil.getParamGroupJavaClass(annotation);
for (String javaClass : groupClassList) {
if (groupClasses.contains(javaClass)) {
fieldJsonAnnotationInfo.setIgnore(true);
return fieldJsonAnnotationInfo;
}
}
}

// Handle @JSONField
if (DocAnnotationConstants.SHORT_JSON_FIELD.equals(annotationName)) {
if (null != annotation.getProperty(DocAnnotationConstants.NAME_PROP)) {
fieldJsonAnnotationInfo.setFieldName(StringUtil
.removeQuotes(annotation.getProperty(DocAnnotationConstants.NAME_PROP).toString()));
}
}

// Handle @JsonProperty
else if (DocAnnotationConstants.SHORT_JSON_PROPERTY.equals(annotationName)
|| DocAnnotationConstants.GSON_ALIAS_NAME.equals(annotationName)) {
AnnotationValue annotationValue = annotation.getProperty(DocAnnotationConstants.VALUE_PROP);
if (null != annotationValue) {
fieldJsonAnnotationInfo.setFieldName(StringUtil.removeQuotes(annotationValue.toString()));
}
}
// Handle JSR303 required
if (JavaClassValidateUtil.isJSR303Required(annotationName) && !isResp) {
Set<String> groupClassList = JavaClassUtil.getParamGroupJavaClass(annotation);
// Check if groupClasses contains any element from
// groupClassList
boolean hasGroup = groupClassList.stream().anyMatch(groupClasses::contains);

if (hasGroup) {
fieldJsonAnnotationInfo.setStrRequired(true);
}
else if (CollectionUtil.isEmpty(groupClasses)) {
// If the annotation is @Valid or @Validated, the Default
// group is added by default and groupClasses will not be
// empty;
// In other cases, if groupClasses is still empty, then
// strRequired is false.
fieldJsonAnnotationInfo.setStrRequired(false);
}
}
// Handle @JsonFormat
if (DocAnnotationConstants.JSON_FORMAT.equals(annotationName)) {
fieldJsonAnnotationInfo.setFieldJsonFormatType(
DocUtil.processFieldTypeNameByJsonFormat(projectBuilder.getApiConfig().getShowJavaType(),
docField.getTypeFullyQualifiedName(), annotation));
fieldJsonAnnotationInfo
.setFieldJsonFormatValue(DocUtil.getJsonFormatString(docField.getJavaField(), annotation));
}

}
return fieldJsonAnnotationInfo;
}

/**
* Get the custom field information for a given field.
* @param projectBuilder the project builder
* @param docField the doc of java field
* @param customResponseField the custom response field
* @param customRequestField the custom request field
* @param isResp the response flag for the parameter
* @param simpleName the simple name of the field
* @return the custom field information {@link CustomFieldInfo}
*
*/
protected static CustomFieldInfo getCustomFieldInfo(ProjectDocConfigBuilder projectBuilder, DocJavaField docField,
CustomField customResponseField, CustomField customRequestField, boolean isResp, String simpleName) {
CustomFieldInfo customFieldInfo = new CustomFieldInfo();

// ignore custom field, if true return quickly
if (isIgnoreCustomField(docField, isResp, customRequestField, customResponseField)) {
customFieldInfo.setIgnore(true);
return customFieldInfo;
}

// cover response value
if (Objects.nonNull(customResponseField) && isResp && Objects.nonNull(customResponseField.getValue())
&& JavaClassUtil.isTargetChildClass(simpleName, customResponseField.getOwnerClassName())) {

customFieldInfo.setFieldValue(String.valueOf(customResponseField.getValue()));
}

// cover request value
if (Objects.nonNull(customRequestField) && !isResp && Objects.nonNull(customRequestField.getValue())
&& JavaClassUtil.isTargetChildClass(simpleName, customRequestField.getOwnerClassName())) {

customFieldInfo.setFieldValue(String.valueOf(customRequestField.getValue()));
}

// cover required
if (Objects.nonNull(customRequestField) && !isResp
&& JavaClassUtil.isTargetChildClass(simpleName, customRequestField.getOwnerClassName())
&& customRequestField.isRequire()) {

customFieldInfo.setStrRequired(true);
}

// cover comment
if (Objects.nonNull(customRequestField) && StringUtil.isNotEmpty(customRequestField.getDesc())
&& JavaClassUtil.isTargetChildClass(simpleName, customRequestField.getOwnerClassName()) && !isResp) {
customFieldInfo.setComment(customRequestField.getDesc());
}
if (Objects.nonNull(customResponseField) && StringUtil.isNotEmpty(customResponseField.getDesc())
&& JavaClassUtil.isTargetChildClass(simpleName, customResponseField.getOwnerClassName()) && isResp) {
customFieldInfo.setComment(customResponseField.getDesc());
}

// cover fieldName
if (Objects.nonNull(customRequestField) && StringUtil.isNotEmpty(customRequestField.getReplaceName())
&& JavaClassUtil.isTargetChildClass(simpleName, customRequestField.getOwnerClassName()) && !isResp) {
customFieldInfo.setFieldName(customRequestField.getReplaceName());
}
if (Objects.nonNull(customResponseField) && StringUtil.isNotEmpty(customResponseField.getReplaceName())
&& JavaClassUtil.isTargetChildClass(simpleName, customResponseField.getOwnerClassName()) && isResp) {

customFieldInfo.setFieldName(customResponseField.getReplaceName());
}
return customFieldInfo;
}

}
Loading

0 comments on commit 8ed7b32

Please sign in to comment.