New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enum should be translated using .name(), not .toString() #2048
Comments
See this: #1247 (comment) - I was dealing with the same issue before. |
The default import java.util.Iterator;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverter;
import io.swagger.v3.core.converter.ModelConverterContext;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import org.springdoc.core.providers.ObjectMapperProvider;
import org.springframework.stereotype.Component;
@Component
public class EnumToNameConverter implements ModelConverter {
private final ObjectMapperProvider springDocObjectMapper;
public EnumToNameConverter(ObjectMapperProvider springDocObjectMapper) {
this.springDocObjectMapper = springDocObjectMapper;
}
@Override
public Schema<?> resolve(
AnnotatedType type,
ModelConverterContext context,
Iterator<ModelConverter> chain) {
ObjectMapper mapper = springDocObjectMapper.jsonMapper();
JavaType javaType = mapper.constructType(type.getType());
if (javaType != null && javaType.isEnumType()) {
Class<Enum> enumClass = (Class<Enum>) javaType.getRawClass();
Enum[] enumConstants = enumClass.getEnumConstants();
StringSchema stringSchema = new StringSchema();
for (Enum en : enumConstants) {
String enumValue = en.name();
stringSchema.addEnumItem(enumValue);
}
return stringSchema;
}
return chain.hasNext() ? chain.next().resolve(type, context, chain) : null;
}
} |
Here is my solution based on response of @bnasslahsen In my case I had to support "string enums" with special characters ( like For example: Original Kotlin enum: enum class Permission(val value: String) {
USER_CREATE("User:create"),
;
override fun toString(): String {
return value // Used by EnumToNameConverter
}
} Generated TypeScript enum: export enum Permission {
USER_CREATE = 'User:create',
} To support the string enum in request query params (Spring): import org.springframework.core.convert.converter.Converter
import org.springframework.web.util.UriUtils
import java.nio.charset.Charset
class PermissionConverter : Converter<String, Permission> {
override fun convert(source: String): Permission {
val decoded = UriUtils.decode(source, Charset.defaultCharset())
return Permission.fromString(decoded)
}
} To support the string enum in request JSON: import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
class PermissionDeserializer : StdDeserializer<Permission>(Permission::class.java) {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Permission {
val value = p.readValueAs(String::class.java)
return Permission.fromString(value)
}
} To support the string enum in response JSON: import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.ser.std.StdSerializer
class PermissionSerializer : StdSerializer<Permission>(Permission::class.java) {
override fun serialize(value: Permission, gen: JsonGenerator, provider: SerializerProvider) {
gen.writeString(value.value)
}
} Finally the model converter: import com.fasterxml.jackson.databind.ObjectMapper
import io.swagger.v3.core.converter.AnnotatedType
import io.swagger.v3.core.converter.ModelConverter
import io.swagger.v3.core.converter.ModelConverterContext
import io.swagger.v3.core.jackson.ModelResolver
import io.swagger.v3.oas.models.Components
import io.swagger.v3.oas.models.media.Schema
import io.swagger.v3.oas.models.media.StringSchema
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
@Component
class EnumToNameConverter(
private val objectMapper: ObjectMapper
) : ModelConverter {
override fun resolve(
type: AnnotatedType,
context: ModelConverterContext,
chain: Iterator<ModelConverter>
): Schema<*>? {
val javaType = objectMapper.constructType(type.type)
if (javaType != null && javaType.isEnumType) {
@Suppress("UNCHECKED_CAST")
val enumClass: Class<Enum<*>> = javaType.rawClass as Class<Enum<*>>
val enumConstants = enumClass.enumConstants
val stringSchema = StringSchema()
val enumNames = mutableListOf<String>()
for (en in enumConstants) {
val enumName = en.name
val enumValue = en.toString()
enumNames.add(enumName)
stringSchema.addEnumItem(enumValue)
}
val extensions = stringSchema.extensions ?: mutableMapOf()
stringSchema.extensions = extensions
extensions["x-enumNames"] = enumNames
if (ModelResolver.enumsAsRef) {
context.defineModel(enumClass.simpleName, stringSchema, type, null)
val refSchema = Schema<Any>()
refSchema.`$ref` = Components.COMPONENTS_SCHEMAS_REF + enumClass.simpleName
return refSchema
}
return stringSchema
}
if (chain.hasNext()) {
return chain.next().resolve(type, context, chain)
}
return null
}
} The solution uses extension components:
schemas:
Permission:
type: string
enum:
- User:create
x-enumNames:
- USER_CREATE Note: The generated "native" TypeScript enum would look like this: export enum Permission {
UserCreate = 'User:create',
} (At the end of the day it is not a real issue. What's more, it is a feature. At least each platform uses the same "constants".) |
Versions:
spring boot 2.7.4
java 17.0.4
springdoc-openapi-ui:1.6.14
Currently Enums are "translated" using their
.toString()
method.results in
Describe the solution you'd like
This current workaround is to add
@JsonProperty(value = "Foo")
to each Enum value, but I think using Enum.name() would be better solution just as proposed and fixed in Springfox springfox/springfox#2860The text was updated successfully, but these errors were encountered: