Repository: victools/jsonschema-generator
Pull request #26 (2020-02-18 21:50:35)

Introduce new Option.FLATTENED_ENUMS_FROM_TOSTRING
Also introducing explicit overrides between related options.

While the new `Option.FLATTENED_ENUMS_FROM_TOSTRING` is not being added to any existing `OptionPreset` by default – to ensure backward-compatibility – it is treated as the one with highest priority of the three enum related options:
- `Option.SIMPLIFIED_ENUMS`
- `Option.FLATTENED_ENUMS`
- `Option.FLATTENED_ENUMS_FROM_TOSTRING`

That means, the other options don't need to be explicitly disabled to use the new `Option`.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6b0fbef..e17e3a3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
+### Added
+- new `Option.FLATTENED_ENUMS_FROM_TOSTRING`, using `toString()` instead of `name()` as per `Option.FLATTENED_ENUMS`
 
 ## [4.0.2] - 2020-01-30
 ### Fixed
diff --git a/src/main/java/com/github/victools/jsonschema/generator/Option.java b/src/main/java/com/github/victools/jsonschema/generator/Option.java
index a55e25d..2a5b52b 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/Option.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/Option.java
@@ -23,7 +23,11 @@ import com.github.victools.jsonschema.generator.impl.module.FlattenedOptionalMod
 import com.github.victools.jsonschema.generator.impl.module.MethodExclusionModule;
 import com.github.victools.jsonschema.generator.impl.module.SimpleTypeModule;
 import com.github.victools.jsonschema.generator.impl.module.SimplifiedOptionalModule;
+import java.util.Collections;
+import java.util.Set;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Configuration options to be set on a {@link SchemaGeneratorConfigBuilder} instance.
@@ -39,23 +43,32 @@ public enum Option {
      */
     ADDITIONAL_FIXED_TYPES(SimpleTypeModule::forPrimitiveAndAdditionalTypes, SimpleTypeModule::forPrimitiveTypes),
     /**
-     * Whether enums should be treated as plain "{@value SchemaConstants#TAG_TYPE_STRING}" values.
+     * Whether enums should be treated as "{@value SchemaConstants#TAG_TYPE_OBJECT}", with all methods but {@link Enum#name() name()} being excluded.
+     * <br>
+     * This only takes effect if {@link Option#FLATTENED_ENUMS} and {@link Option#FLATTENED_ENUMS_FROM_TOSTRING} are disabled.
      *
-     * @see Option#SIMPLIFIED_ENUMS
+     * @see Option#FLATTENED_ENUMS
+     * @see Option#FLATTENED_ENUMS_FROM_TOSTRING
      */
-    FLATTENED_ENUMS(EnumModule::asStrings, null),
+    SIMPLIFIED_ENUMS(EnumModule::asObjects, null),
     /**
-     * Whether enums should be treated as "{@value SchemaConstants#TAG_TYPE_OBJECT}", with all methods but {@link Enum#name() name()} being excluded.
+     * Whether enums should be treated as plain "{@value SchemaConstants#TAG_TYPE_STRING}" values – derived from their respective constant name.
      * <br>
-     * This only takes effect if {@link Option#FLATTENED_ENUMS} is disabled.
+     * This only takes effect if {@link Option#FLATTENED_ENUMS_FROM_TOSTRING} is disabled but takes priority over {@link Option#SIMPLIFIED_ENUMS}.
+     *
+     * @see Option#FLATTENED_ENUMS_FROM_TOSTRING
+     * @see Option#SIMPLIFIED_ENUMS
      */
-    SIMPLIFIED_ENUMS(EnumModule::asObjects, null),
+    FLATTENED_ENUMS(EnumModule::asStringsFromName, null, Option.SIMPLIFIED_ENUMS),
     /**
-     * Whether any {@link java.util.Optional Optional} instance should be treated as nullable value of the wrapped type.
+     * Whether enums should be treated as plain "{@value SchemaConstants#TAG_TYPE_STRING}" values – derived from their respective {@code toString()}.
+     * <br>
+     * This takes priority over both {@link Option#FLATTENED_ENUMS} and {@link Option#SIMPLIFIED_ENUMS}.
      *
-     * @see Option#SIMPLIFIED_OPTIONALS
+     * @see Option#FLATTENED_ENUMS
+     * @see Option#SIMPLIFIED_ENUMS
      */
-    FLATTENED_OPTIONALS(FlattenedOptionalModule::new, null),
+    FLATTENED_ENUMS_FROM_TOSTRING(EnumModule::asStringsFromToString, null, Option.FLATTENED_ENUMS, Option.SIMPLIFIED_ENUMS),
     /**
      * Whether any {@link java.util.Optional Optional} instance should be reduced to an object with only three methods.
      * <br>
@@ -64,6 +77,14 @@ public enum Option {
      * @see SimplifiedOptionalModule#DEFAULT_INCLUDED_METHOD_NAMES
      */
     SIMPLIFIED_OPTIONALS(SimplifiedOptionalModule::new, null),
+    /**
+     * Whether any {@link java.util.Optional Optional} instance should be treated as nullable value of the wrapped type.
+     * <br>
+     * This takes priority over {@link Option#SIMPLIFIED_OPTIONALS}.
+     *
+     * @see Option#SIMPLIFIED_OPTIONALS
+     */
+    FLATTENED_OPTIONALS(FlattenedOptionalModule::new, null, Option.SIMPLIFIED_OPTIONALS),
     /**
      * Whether the constant values of static final fields should be included.
      */
@@ -187,16 +208,36 @@ public enum Option {
      * Optional: the module realising the setting/option if it is disabled.
      */
     private final Supplier<Module> disabledModuleProvider;
+    /**
+     * Other options being ignored if this one is enabled.
+     */
+    private final Set<Option> overriddenOptions;
 
     /**
      * Constructor.
      *
      * @param enabledModuleProvider type of the module realising this setting/option if it is enabled
      * @param disabledModuleProvider type of the module realising this setting/option if it is disabled
+     * @param overriddenOptions other options being ignored if this one is enabled
      */
-    private Option(Supplier<Module> enabledModuleProvider, Supplier<Module> disabledModuleProvider) {
+    private Option(Supplier<Module> enabledModuleProvider, Supplier<Module> disabledModuleProvider, Option... overriddenOptions) {
         this.enabledModuleProvider = enabledModuleProvider;
         this.disabledModuleProvider = disabledModuleProvider;
+        if (overriddenOptions == null || overriddenOptions.length == 0) {
+            this.overriddenOptions = Collections.emptySet();
+        } else {
+            this.overriddenOptions = Stream.of(overriddenOptions).collect(Collectors.toSet());
+        }
+    }
+
+    /**
+     * Check whether the given option is being ignored if this one enabled.
+     *
+     * @param otherOption option that may be ignored
+     * @return whether the given option is being ignored in case of this one being enabled
+     */
+    public boolean isOverriding(Option otherOption) {
+        return this.overriddenOptions.contains(otherOption);
     }
 
     /**
diff --git a/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfigBuilder.java b/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfigBuilder.java
index 16127ca..8f72293 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfigBuilder.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfigBuilder.java
@@ -24,6 +24,8 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
@@ -74,8 +76,15 @@ public class SchemaGeneratorConfigBuilder {
                 .collect(Collectors.toMap(
                         option -> option,
                         option -> this.options.getOrDefault(option, this.preset.isOptionEnabledByDefault(option))));
+        Set<Option> enabledOptions = completeSetOfOptions.entrySet()
+                .stream()
+                .filter(Map.Entry::getValue)
+                .map(Map.Entry::getKey)
+                .collect(Collectors.toSet());
+        Predicate<Option> isValid = (configuredOption) -> enabledOptions.stream().noneMatch(enabledOne -> enabledOne.isOverriding(configuredOption));
         completeSetOfOptions.entrySet()
                 .stream()
+                .filter(setting -> !setting.getValue() || isValid.test(setting.getKey()))
                 .map(setting -> setting.getKey().getModule(setting.getValue()))
                 .filter(Objects::nonNull)
                 .forEach(this::with);
diff --git a/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java b/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java
index d1f8f38..03c5f2c 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java
@@ -17,10 +17,10 @@
 package com.github.victools.jsonschema.generator.impl.module;
 
 import com.fasterxml.classmate.ResolvedType;
-import com.fasterxml.classmate.members.RawField;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.github.victools.jsonschema.generator.CustomDefinition;
+import com.github.victools.jsonschema.generator.CustomDefinitionProvider;
 import com.github.victools.jsonschema.generator.CustomDefinitionProviderV2;
 import com.github.victools.jsonschema.generator.MethodScope;
 import com.github.victools.jsonschema.generator.Module;
@@ -29,7 +29,9 @@ import com.github.victools.jsonschema.generator.SchemaGenerationContext;
 import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
 import com.github.victools.jsonschema.generator.impl.AttributeCollector;
 import java.util.List;
+import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Default module being included for the {@code Option.ENUM_AS_STRING}.
@@ -37,12 +39,21 @@ import java.util.stream.Collectors;
 public class EnumModule implements Module {
 
     /**
-     * Factory method: creating an {@link EnumModule} instance that treats all enums as plain strings.
+     * Factory method: creating an {@link EnumModule} instance that treats all enums as plain strings (derived from their constant value names).
      *
      * @return created module instance
      */
-    public static EnumModule asStrings() {
-        return new EnumModule(true);
+    public static EnumModule asStringsFromName() {
+        return new EnumModule(Enum::name);
+    }
+
+    /**
+     * Factory method: creating an {@link EnumModule} instance that treats all enums as plain strings (derived from each value's toString()).
+     *
+     * @return created module instance
+     */
+    public static EnumModule asStringsFromToString() {
+        return new EnumModule(Enum::toString);
     }
 
     /**
@@ -52,25 +63,23 @@ public class EnumModule implements Module {
      * @return created module instance
      */
     public static EnumModule asObjects() {
-        return new EnumModule(false);
+        return new EnumModule(null);
     }
 
-    private final boolean treatAsString;
+    private final Function<Enum<?>, String> enumConstantToString;
 
     /**
      * Constructor remembering whether to treat enums as plain strings or as objects.
      *
-     * @param treatAsString whether to treat enums as plain strings
+     * @param enumConstantToString how to derive a plain string representation from an enum constant value, may be null to treat them as objects
      */
-    public EnumModule(boolean treatAsString) {
-        this.treatAsString = treatAsString;
+    public EnumModule(Function<Enum<?>, String> enumConstantToString) {
+        this.enumConstantToString = enumConstantToString;
     }
 
     @Override
     public void applyToConfigBuilder(SchemaGeneratorConfigBuilder builder) {
-        if (this.treatAsString) {
-            builder.with(new EnumAsStringDefinitionProvider(builder.getObjectMapper()));
-        } else {
+        if (this.enumConstantToString == null) {
             // ignore all direct enum methods but name() - methods declared by a specific enum sub type are not ignored
             builder.forMethods()
                     .withIgnoreCheck(method -> EnumModule.isEnum(method.getDeclaringType()) && !"name".equals(method.getName()))
@@ -78,6 +87,8 @@ public class EnumModule implements Module {
                     .withEnumResolver(EnumModule::extractEnumValues);
             builder.forFields()
                     .withIgnoreCheck(field -> field.getRawMember().isEnumConstant());
+        } else {
+            builder.with(new EnumAsStringDefinitionProvider(builder.getObjectMapper(), this.enumConstantToString));
         }
     }
 
@@ -94,7 +105,7 @@ public class EnumModule implements Module {
     private static List<String> extractEnumValues(MethodScope method) {
         ResolvedType declaringType = method.getDeclaringType();
         if (EnumModule.isEnum(declaringType)) {
-            return EnumModule.extractEnumValues(declaringType.getTypeParameters().get(0));
+            return EnumModule.extractEnumValues(declaringType.getTypeParameters().get(0), Enum::name);
         }
         return null;
     }
@@ -102,15 +113,13 @@ public class EnumModule implements Module {
     /**
      * Look-up the given enum type's constant values.
      *
-     * @param <E> specific enum type
      * @param enumType targeted enum type
+     * @param enumConstantToString how to derive a plain string representation from an enum constant value
      * @return collection containing constant enum values
      */
-    private static <E extends Enum<E>> List<String> extractEnumValues(ResolvedType enumType) {
-        return enumType.getStaticFields()
-                .stream()
-                .filter(field -> field.getRawMember().isEnumConstant())
-                .map(RawField::getName)
+    private static List<String> extractEnumValues(ResolvedType enumType, Function<Enum<?>, String> enumConstantToString) {
+        return Stream.of(enumType.getErasedType().getEnumConstants())
+                .map(enumConstant -> enumConstantToString.apply((Enum<?>) enumConstant))
                 .collect(Collectors.toList());
     }
 
@@ -120,14 +129,17 @@ public class EnumModule implements Module {
     private static class EnumAsStringDefinitionProvider implements CustomDefinitionProviderV2 {
 
         private final ObjectMapper objectMapper;
+        private final Function<Enum<?>, String> enumConstantToString;
 
         /**
          * Constructor setting the given object mapper for later use as ObjectNode prodiver.
          *
          * @param objectMapper object node provider
+         * @param enumConstantToString how to derive a plain string representation from an enum constant value
          */
-        EnumAsStringDefinitionProvider(ObjectMapper objectMapper) {
+        EnumAsStringDefinitionProvider(ObjectMapper objectMapper, Function<Enum<?>, String> enumConstantToString) {
             this.objectMapper = objectMapper;
+            this.enumConstantToString = enumConstantToString;
         }
 
         @Override
@@ -136,7 +148,7 @@ public class EnumModule implements Module {
                 ObjectNode customNode = this.objectMapper.createObjectNode()
                         .put(SchemaConstants.TAG_TYPE, SchemaConstants.TAG_TYPE_STRING);
                 new AttributeCollector(this.objectMapper)
-                        .setEnum(customNode, EnumModule.extractEnumValues(javaType));
+                        .setEnum(customNode, EnumModule.extractEnumValues(javaType, this.enumConstantToString));
                 return new CustomDefinition(customNode);
             }
             return null;


Test patch:
diff --git a/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorTest.java b/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorTest.java
index 03e8013..4a6c1c9 100644
--- a/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorTest.java
+++ b/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorTest.java
@@ -189,6 +189,7 @@ public class SchemaGeneratorTest {
         Module typeInGeneralModule = configBuilder -> populateTypeConfigPart(configBuilder.forTypesInGeneral(), "for type in general: ");
         Module methodModule = configBuilder -> populateConfigPart(configBuilder.forMethods(), "looked-up from method: ");
         Module fieldModule = configBuilder -> populateConfigPart(configBuilder.forFields(), "looked-up from field: ");
+        Module enumToStringModule = configBuilder -> configBuilder.with(Option.FLATTENED_ENUMS_FROM_TOSTRING);
         return new Object[][]{
             {"testclass1-FULL_DOCUMENTATION", OptionPreset.FULL_DOCUMENTATION, TestClass1.class, neutralModule},
             {"testclass1-FULL_DOCUMENTATION-attributes", OptionPreset.FULL_DOCUMENTATION, TestClass1.class, typeInGeneralModule},
@@ -198,7 +199,10 @@ public class SchemaGeneratorTest {
             {"testclass3-FULL_DOCUMENTATION", OptionPreset.FULL_DOCUMENTATION, TestClass3.class, neutralModule},
             {"testclass3-FULL_DOCUMENTATION-attributes", OptionPreset.FULL_DOCUMENTATION, TestClass3.class, typeInGeneralModule},
             {"testclass3-JAVA_OBJECT-attributes", OptionPreset.JAVA_OBJECT, TestClass3.class, methodModule},
-            {"testclass3-PLAIN_JSON-attributes", OptionPreset.PLAIN_JSON, TestClass3.class, fieldModule}
+            {"testclass3-PLAIN_JSON-attributes", OptionPreset.PLAIN_JSON, TestClass3.class, fieldModule},
+            {"testenum-PLAIN_JSON-default", OptionPreset.PLAIN_JSON, TestEnum.class, neutralModule},
+            {"testenum-FULL_DOCUMENTATION-default", OptionPreset.FULL_DOCUMENTATION, TestEnum.class, neutralModule},
+            {"testenum-PLAIN_JSON-viaToString", OptionPreset.PLAIN_JSON, TestEnum.class, enumToStringModule}
         };
     }
 
@@ -302,4 +306,13 @@ public class SchemaGeneratorTest {
             return this.class2OfClass2OfT;
         }
     }
+
+    private static enum TestEnum {
+        VALUE1, VALUE2, VALUE3;
+
+        @Override
+        public String toString() {
+            return "toString_" + this.name();
+        }
+    }
 }
diff --git a/src/test/java/com/github/victools/jsonschema/generator/impl/module/EnumModuleTest.java b/src/test/java/com/github/victools/jsonschema/generator/impl/module/EnumModuleTest.java
index 23722c3..376e4b2 100644
--- a/src/test/java/com/github/victools/jsonschema/generator/impl/module/EnumModuleTest.java
+++ b/src/test/java/com/github/victools/jsonschema/generator/impl/module/EnumModuleTest.java
@@ -42,7 +42,8 @@ import org.mockito.Mockito;
  */
 public class EnumModuleTest extends AbstractTypeAwareTest {
 
-    private EnumModule instanceAsStrings;
+    private EnumModule instanceAsStringsFromName;
+    private EnumModule instanceAsStringsFromToString;
     private EnumModule instanceAsObjects;
     private SchemaGeneratorConfigBuilder builder;
     private SchemaGeneratorConfigPart<FieldScope> fieldConfigPart;
@@ -54,7 +55,8 @@ public class EnumModuleTest extends AbstractTypeAwareTest {
 
     @Before
     public void setUp() {
-        this.instanceAsStrings = EnumModule.asStrings();
+        this.instanceAsStringsFromName = EnumModule.asStringsFromName();
+        this.instanceAsStringsFromToString = EnumModule.asStringsFromToString();
         this.instanceAsObjects = EnumModule.asObjects();
         this.builder = Mockito.spy(new SchemaGeneratorConfigBuilder(new ObjectMapper(), new OptionPreset()));
         this.fieldConfigPart = Mockito.spy(new SchemaGeneratorConfigPart<>());
@@ -65,8 +67,18 @@ public class EnumModuleTest extends AbstractTypeAwareTest {
 
     @Test
     @SuppressWarnings("unchecked")
-    public void testApplyToConfigBuilder_asStrings() {
-        this.instanceAsStrings.applyToConfigBuilder(this.builder);
+    public void testApplyToConfigBuilder_asStringsFromName() {
+        this.instanceAsStringsFromName.applyToConfigBuilder(this.builder);
+
+        Mockito.verify(this.builder).getObjectMapper();
+        Mockito.verify(this.builder).with(Mockito.any(CustomDefinitionProviderV2.class));
+        Mockito.verifyNoMoreInteractions(this.builder);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testApplyToConfigBuilder_asStringsFromToString() {
+        this.instanceAsStringsFromToString.applyToConfigBuilder(this.builder);
 
         Mockito.verify(this.builder).getObjectMapper();
         Mockito.verify(this.builder).with(Mockito.any(CustomDefinitionProviderV2.class));
@@ -90,8 +102,8 @@ public class EnumModuleTest extends AbstractTypeAwareTest {
     }
 
     @Test
-    public void testCustomSchemaDefinition_asStrings() {
-        this.instanceAsStrings.applyToConfigBuilder(this.builder);
+    public void testCustomSchemaDefinition_asStringsFromName() {
+        this.instanceAsStringsFromName.applyToConfigBuilder(this.builder);
         ArgumentCaptor<CustomDefinitionProviderV2> captor = ArgumentCaptor.forClass(CustomDefinitionProviderV2.class);
         Mockito.verify(this.builder).with(captor.capture());
 
@@ -116,7 +128,39 @@ public class EnumModuleTest extends AbstractTypeAwareTest {
         Assert.assertEquals("VALUE3", enumNode.get(2).textValue());
     }
 
+    @Test
+    public void testCustomSchemaDefinition_asStringsFromToString() {
+        this.instanceAsStringsFromToString.applyToConfigBuilder(this.builder);
+        ArgumentCaptor<CustomDefinitionProviderV2> captor = ArgumentCaptor.forClass(CustomDefinitionProviderV2.class);
+        Mockito.verify(this.builder).with(captor.capture());
+
+        ResolvedType testEnumType = this.getContext().getTypeContext().resolve(TestEnum.class);
+        CustomDefinition schemaDefinition = captor.getValue().provideCustomSchemaDefinition(testEnumType, this.getContext());
+        Assert.assertFalse(schemaDefinition.isMeantToBeInline());
+        ObjectNode node = schemaDefinition.getValue();
+        Assert.assertEquals(2, node.size());
+
+        JsonNode typeNode = node.get(SchemaConstants.TAG_TYPE);
+        Assert.assertEquals(JsonNodeType.STRING, typeNode.getNodeType());
+        Assert.assertEquals(SchemaConstants.TAG_TYPE_STRING, typeNode.textValue());
+
+        JsonNode enumNode = node.get(SchemaConstants.TAG_ENUM);
+        Assert.assertEquals(JsonNodeType.ARRAY, enumNode.getNodeType());
+        Assert.assertEquals(3, ((ArrayNode) enumNode).size());
+        Assert.assertEquals(JsonNodeType.STRING, enumNode.get(0).getNodeType());
+        Assert.assertEquals("value1_toString", enumNode.get(0).textValue());
+        Assert.assertEquals(JsonNodeType.STRING, enumNode.get(1).getNodeType());
+        Assert.assertEquals("value2_toString", enumNode.get(1).textValue());
+        Assert.assertEquals(JsonNodeType.STRING, enumNode.get(2).getNodeType());
+        Assert.assertEquals("value3_toString", enumNode.get(2).textValue());
+    }
+
     private static enum TestEnum {
         VALUE1, VALUE2, VALUE3;
+
+        @Override
+        public String toString() {
+            return this.name().toLowerCase() + "_toString";
+        }
     }
 }
diff --git a/src/test/resources/com/github/victools/jsonschema/generator/testenum-FULL_DOCUMENTATION-default.json b/src/test/resources/com/github/victools/jsonschema/generator/testenum-FULL_DOCUMENTATION-default.json
new file mode 100644
index 0000000..5f701fe
--- /dev/null
+++ b/src/test/resources/com/github/victools/jsonschema/generator/testenum-FULL_DOCUMENTATION-default.json
@@ -0,0 +1,39 @@
+{
+    "definitions": {
+        "TestEnum-nullable": {
+            "oneOf": [{
+                    "type": "null"
+                }, {
+                    "$ref": "#"
+                }]
+        }
+    },
+    "type": "object",
+    "properties": {
+        "name": {
+            "type": ["string", "null"]
+        },
+        "ordinal": {
+            "type": "integer"
+        },
+        "compareTo(TestEnum)": {
+            "type": "integer"
+        },
+        "name()": {
+            "type": "string",
+            "enum": ["VALUE1", "VALUE2", "VALUE3"]
+        },
+        "toString()": {
+            "type": ["string", "null"]
+        },
+        "valueOf(String)": {
+            "$ref": "#/definitions/TestEnum-nullable"
+        },
+        "values()": {
+            "type": ["array", "null"],
+            "items": {
+                "$ref": "#"
+            }
+        }
+    }
+}
diff --git a/src/test/resources/com/github/victools/jsonschema/generator/testenum-PLAIN_JSON-default.json b/src/test/resources/com/github/victools/jsonschema/generator/testenum-PLAIN_JSON-default.json
new file mode 100644
index 0000000..fce729a
--- /dev/null
+++ b/src/test/resources/com/github/victools/jsonschema/generator/testenum-PLAIN_JSON-default.json
@@ -0,0 +1,5 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "string",
+    "enum": ["VALUE1", "VALUE2", "VALUE3"]
+}
diff --git a/src/test/resources/com/github/victools/jsonschema/generator/testenum-PLAIN_JSON-viaToString.json b/src/test/resources/com/github/victools/jsonschema/generator/testenum-PLAIN_JSON-viaToString.json
new file mode 100644
index 0000000..4dedb73
--- /dev/null
+++ b/src/test/resources/com/github/victools/jsonschema/generator/testenum-PLAIN_JSON-viaToString.json
@@ -0,0 +1,5 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "string",
+    "enum": ["toString_VALUE1", "toString_VALUE2", "toString_VALUE3"]
+}