Repository: victools/jsonschema-generator
Pull request #31 (2020-02-23 18:46:26)

Support configuration for patternProperties
In the scope of the support for `additionalProperties` (#22), the discussion touched upon the related `patternProperties`.
As they are very similar, adding the support for `patternProperties` here is not much trouble.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a47f5b5..9da8e8d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 ### Added
-- Support for "additionalProperties" property via `SchemaGeneratorConfigPart.withAdditionalPropertiesResolver()`
+- Support for "additionalProperties" property via `SchemaGeneratorTypeConfigPart.withAdditionalPropertiesResolver()`
+- Support for "patternProperties" property via `SchemaGeneratorTypeConfigPart.withPatternPropertiesResolver()`
 - Offer `TypeScope.getTypeParameterFor()` and `TypeContext.getTypeParameterFor()` convenience methods
 
 ## [4.1.0] - 2020-02-18
diff --git a/README.md b/README.md
index 61dab2c..cf5d6c2 100644
--- a/README.md
+++ b/README.md
@@ -173,17 +173,18 @@ configBuilder.forTypesInGeneral()
 |   12 | `const` | Collected value according to configuration (`SchemaGeneratorConfigPart.withEnumResolver()`) if only a single value was found. |
 |   13 | `enum` | Collected value according to configuration (`SchemaGeneratorConfigPart.withEnumResolver()`) if multiple values were found. |
 |   14 | `default` | Collected value according to configuration (`SchemaGeneratorConfigPart.withDefaultResolver()`). |
-|   15 | `additionalProperties` | Defining whether additional properties are allowed according to configuration (`SchemaGeneratorConfigPart.withAdditionalPropertiesResolver()`). |
-|   16 | `minLength` | Collected value according to configuration (`SchemaGeneratorConfigPart.withStringMinLengthResolver()`). |
-|   17 | `maxLength` | Collected value according to configuration (`SchemaGeneratorConfigPart.withStringMaxLengthResolver()`). |
-|   18 | `format` | Collected value according to configuration (`SchemaGeneratorConfigPart.withStringFormatResolver()`). |
-|   19 | `pattern` | Collected value according to configuration (`SchemaGeneratorConfigPart.withStringPatternResolver()`). |
-|   20 | `minimum` | Collected value according to configuration (`SchemaGeneratorConfigPart.withNumberInclusiveMinimumResolver()`). |
-|   21 | `exclusiveMinimum` | Collected value according to configuration (`SchemaGeneratorConfigPart.withNumberExclusiveMinimumResolver()`). |
-|   22 | `maximum` | Collected value according to configuration (`SchemaGeneratorConfigPart.withNumberInclusiveMaximumResolver()`). |
-|   23 | `exclusiveMaximum` | Collected value according to configuration (`SchemaGeneratorConfigPart.withNumberExclusiveMaximumResolver()`). |
-|   24 | `multipleOf` | Collected value according to configuration (`SchemaGeneratorConfigPart.withNumberMultipleOfResolver()`). |
-|   25 | `minItems` | Collected value according to configuration (`SchemaGeneratorConfigPart.withArrayMinItemsResolver()`). |
-|   26 | `maxItems` | Collected value according to configuration (`SchemaGeneratorConfigPart.withArrayMaxItemsResolver()`). |
-|   27 | `uniqueItems` | Collected value according to configuration (`SchemaGeneratorConfigPart.withArrayUniqueItemsResolver()`). |
-|   28 | any other | You can directly manipulate the generated `ObjectNode` of a sub-schema – e.g. setting additional attributes – via configuration based on a given type in general (`SchemaGeneratorConfigBuilder.with(TypeAttributeOverride)`) and/or in the context of a particular field/method (`SchemaGeneratorConfigPart.withInstanceAttributeOverride()`). |
+|   15 | `additionalProperties` | Collected value according to configuration (`SchemaGeneratorConfigPart.withAdditionalPropertiesResolver()`). |
+|   16 | `patternProperties` | Collected value(s) according to configuration (`SchemaGeneratorConfigPart.withPatternPropertiesResolver()`). |
+|   17 | `minLength` | Collected value according to configuration (`SchemaGeneratorConfigPart.withStringMinLengthResolver()`). |
+|   18 | `maxLength` | Collected value according to configuration (`SchemaGeneratorConfigPart.withStringMaxLengthResolver()`). |
+|   19 | `format` | Collected value according to configuration (`SchemaGeneratorConfigPart.withStringFormatResolver()`). |
+|   20 | `pattern` | Collected value according to configuration (`SchemaGeneratorConfigPart.withStringPatternResolver()`). |
+|   21 | `minimum` | Collected value according to configuration (`SchemaGeneratorConfigPart.withNumberInclusiveMinimumResolver()`). |
+|   22 | `exclusiveMinimum` | Collected value according to configuration (`SchemaGeneratorConfigPart.withNumberExclusiveMinimumResolver()`). |
+|   23 | `maximum` | Collected value according to configuration (`SchemaGeneratorConfigPart.withNumberInclusiveMaximumResolver()`). |
+|   24 | `exclusiveMaximum` | Collected value according to configuration (`SchemaGeneratorConfigPart.withNumberExclusiveMaximumResolver()`). |
+|   25 | `multipleOf` | Collected value according to configuration (`SchemaGeneratorConfigPart.withNumberMultipleOfResolver()`). |
+|   26 | `minItems` | Collected value according to configuration (`SchemaGeneratorConfigPart.withArrayMinItemsResolver()`). |
+|   27 | `maxItems` | Collected value according to configuration (`SchemaGeneratorConfigPart.withArrayMaxItemsResolver()`). |
+|   28 | `uniqueItems` | Collected value according to configuration (`SchemaGeneratorConfigPart.withArrayUniqueItemsResolver()`). |
+|   29 | any other | You can directly manipulate the generated `ObjectNode` of a sub-schema – e.g. setting additional attributes – via configuration based on a given type in general (`SchemaGeneratorConfigBuilder.with(TypeAttributeOverride)`) and/or in the context of a particular field/method (`SchemaGeneratorConfigPart.withInstanceAttributeOverride()`). |
diff --git a/src/main/java/com/github/victools/jsonschema/generator/SchemaConstants.java b/src/main/java/com/github/victools/jsonschema/generator/SchemaConstants.java
index 072c797..92ed9b9 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/SchemaConstants.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/SchemaConstants.java
@@ -41,6 +41,7 @@ public interface SchemaConstants {
     String TAG_ITEMS = "items";
     String TAG_REQUIRED = "required";
     String TAG_ADDITIONAL_PROPERTIES = "additionalProperties";
+    String TAG_PATTERN_PROPERTIES = "patternProperties";
 
     String TAG_ALLOF = "allOf";
     String TAG_ANYOF = "anyOf";
diff --git a/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java b/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java
index 866f34d..b5e996b 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java
@@ -24,6 +24,7 @@ import java.lang.reflect.Type;
 import java.math.BigDecimal;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Default implementation of a schema generator's configuration.
@@ -315,6 +316,30 @@ public interface SchemaGeneratorConfig {
      */
     Type resolveAdditionalPropertiesForType(TypeScope scope);
 
+    /**
+     * Determine the "patternProperties" of an object's field/property.
+     *
+     * @param field object's field/property to determine "patternProperties" value for
+     * @return "patternProperties" in a JSON Schema (may be null), the keys representing the patterns and the mapped values their corresponding types
+     */
+    Map<String, Type> resolvePatternProperties(FieldScope field);
+
+    /**
+     * Determine the "patternProperties" of a method's return value.
+     *
+     * @param method method for whose return value to determine "patternProperties" value for
+     * @return "patternProperties" in a JSON Schema (may be null), the keys representing the patterns and the mapped values their corresponding types
+     */
+    Map<String, Type> resolvePatternProperties(MethodScope method);
+
+    /**
+     * Determine the "patternProperties" of a context-independent type representation.
+     *
+     * @param scope context-independent type representation to determine "patternProperties" value for
+     * @return "patternProperties" in a JSON Schema (may be null), the keys representing the patterns and the mapped values their corresponding types
+     */
+    Map<String, Type> resolvePatternPropertiesForType(TypeScope scope);
+
     /**
      * Determine the "minLength" of an object's field/property.
      *
diff --git a/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfigPart.java b/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfigPart.java
index 3a9ba52..f902ca8 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfigPart.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfigPart.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Predicate;
@@ -207,6 +208,11 @@ public class SchemaGeneratorConfigPart<M extends MemberScope<?, ?>> extends Sche
         return (SchemaGeneratorConfigPart<M>) super.withAdditionalPropertiesResolver(resolver);
     }
 
+    @Override
+    public SchemaGeneratorConfigPart<M> withPatternPropertiesResolver(ConfigFunction<M, Map<String, Type>> resolver) {
+        return (SchemaGeneratorConfigPart<M>) super.withPatternPropertiesResolver(resolver);
+    }
+
     @Override
     public SchemaGeneratorConfigPart<M> withStringMinLengthResolver(ConfigFunction<M, Integer> resolver) {
         return (SchemaGeneratorConfigPart<M>) super.withStringMinLengthResolver(resolver);
diff --git a/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorTypeConfigPart.java b/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorTypeConfigPart.java
index bc6ea71..359f4b0 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorTypeConfigPart.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorTypeConfigPart.java
@@ -21,6 +21,7 @@ import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -55,6 +56,7 @@ public class SchemaGeneratorTypeConfigPart<S extends TypeScope> {
     private final List<ConfigFunction<S, Object>> defaultResolvers = new ArrayList<>();
     private final List<ConfigFunction<S, Collection<?>>> enumResolvers = new ArrayList<>();
     private final List<ConfigFunction<S, Type>> additionalPropertiesResolvers = new ArrayList<>();
+    private final List<ConfigFunction<S, Map<String, Type>>> patternPropertiesResolvers = new ArrayList<>();
 
     /*
      * Validation fields relating to a schema with "type": "string".
@@ -165,7 +167,7 @@ public class SchemaGeneratorTypeConfigPart<S extends TypeScope> {
     }
 
     /**
-     * Setter for "additionalProperties" resolver.
+     * Setter for "additionalProperties" resolver. If the returned type is {@link Void} "false" will be set, otherwise an appropriate sub-schema.
      *
      * @param resolver how to determine the "additionalProperties" of a JSON Schema, returning {@link Void} will result in "false"
      * @return this config part (for chaining)
@@ -185,6 +187,27 @@ public class SchemaGeneratorTypeConfigPart<S extends TypeScope> {
         return getFirstDefinedValue(this.additionalPropertiesResolvers, scope);
     }
 
+    /**
+     * Setter for "patternProperties" resolver. The map's keys are representing the patterns and the mapped values their corresponding types.
+     *
+     * @param resolver how to determine the "patternProperties" of a JSON Schema
+     * @return this config part (for chaining)
+     */
+    public SchemaGeneratorTypeConfigPart<S> withPatternPropertiesResolver(ConfigFunction<S, Map<String, Type>> resolver) {
+        this.patternPropertiesResolvers.add(resolver);
+        return this;
+    }
+
+    /**
+     * Determine the "patternProperties" of a given scope/type representation.
+     *
+     * @param scope scope to determine "patternProperties" value for
+     * @return "patternProperties" in a JSON Schema (may be null), the keys representing the patterns and the mapped values their corresponding types
+     */
+    public Map<String, Type> resolvePatternProperties(S scope) {
+        return getFirstDefinedValue(this.patternPropertiesResolvers, scope);
+    }
+
     /**
      * Setter for "minLength" resolver.
      *
diff --git a/src/main/java/com/github/victools/jsonschema/generator/impl/AttributeCollector.java b/src/main/java/com/github/victools/jsonschema/generator/impl/AttributeCollector.java
index 1462804..9dcdd79 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/impl/AttributeCollector.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/impl/AttributeCollector.java
@@ -31,6 +31,7 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -69,6 +70,7 @@ public class AttributeCollector {
         collector.setDefault(node, config.resolveDefault(field));
         collector.setEnum(node, config.resolveEnum(field));
         collector.setAdditionalProperties(node, config.resolveAdditionalProperties(field), generationContext);
+        collector.setPatternProperties(node, config.resolvePatternProperties(field), generationContext);
         collector.setStringMinLength(node, config.resolveStringMinLength(field));
         collector.setStringMaxLength(node, config.resolveStringMaxLength(field));
         collector.setStringFormat(node, config.resolveStringFormat(field));
@@ -102,6 +104,7 @@ public class AttributeCollector {
         collector.setDefault(node, config.resolveDefault(method));
         collector.setEnum(node, config.resolveEnum(method));
         collector.setAdditionalProperties(node, config.resolveAdditionalProperties(method), generationContext);
+        collector.setPatternProperties(node, config.resolvePatternProperties(method), generationContext);
         collector.setStringMinLength(node, config.resolveStringMinLength(method));
         collector.setStringMaxLength(node, config.resolveStringMaxLength(method));
         collector.setStringFormat(node, config.resolveStringFormat(method));
@@ -135,6 +138,7 @@ public class AttributeCollector {
         collector.setDefault(node, config.resolveDefaultForType(scope));
         collector.setEnum(node, config.resolveEnumForType(scope));
         collector.setAdditionalProperties(node, config.resolveAdditionalPropertiesForType(scope), generationContext);
+        collector.setPatternProperties(node, config.resolvePatternPropertiesForType(scope), generationContext);
         collector.setStringMinLength(node, config.resolveStringMinLengthForType(scope));
         collector.setStringMaxLength(node, config.resolveStringMaxLengthForType(scope));
         collector.setStringFormat(node, config.resolveStringFormatForType(scope));
@@ -297,6 +301,29 @@ public class AttributeCollector {
         return this;
     }
 
+    /**
+     * Setter for "{@value SchemaConstants#TAG_PATTERN_PROPERTIES}" attribute.
+     *
+     * @param node schema node to set attribute on
+     * @param patternProperties resolved attribute value to set
+     * @param generationContext generation context allowing for standard definitions to be included as attributes
+     * @return this instance (for chaining)
+     */
+    public AttributeCollector setPatternProperties(ObjectNode node, Map<String, Type> patternProperties,
+            SchemaGenerationContextImpl generationContext) {
+        if (patternProperties != null && !patternProperties.isEmpty()) {
+            ObjectNode patternPropertiesNode = generationContext.getGeneratorConfig().createObjectNode();
+            for (Map.Entry<String, Type> entry : patternProperties.entrySet()) {
+                ObjectNode singlePatternSchema = generationContext.getGeneratorConfig().createObjectNode();
+                ResolvedType targetType = generationContext.getTypeContext().resolve(entry.getValue());
+                generationContext.traverseGenericType(targetType, singlePatternSchema, false);
+                patternPropertiesNode.set(entry.getKey(), singlePatternSchema);
+            }
+            node.set(SchemaConstants.TAG_PATTERN_PROPERTIES, patternPropertiesNode);
+        }
+        return this;
+    }
+
     /**
      * Setter for "{@value SchemaConstants#TAG_LENGTH_MIN}" attribute.
      *
diff --git a/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java b/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java
index c5c03b8..ca4067e 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java
@@ -285,6 +285,21 @@ public class SchemaGeneratorConfigImpl implements SchemaGeneratorConfig {
         return this.typesInGeneralConfigPart.resolveAdditionalProperties(scope);
     }
 
+    @Override
+    public Map<String, Type> resolvePatternProperties(FieldScope field) {
+        return this.fieldConfigPart.resolvePatternProperties(field);
+    }
+
+    @Override
+    public Map<String, Type> resolvePatternProperties(MethodScope method) {
+        return this.methodConfigPart.resolvePatternProperties(method);
+    }
+
+    @Override
+    public Map<String, Type> resolvePatternPropertiesForType(TypeScope scope) {
+        return this.typesInGeneralConfigPart.resolvePatternProperties(scope);
+    }
+
     @Override
     public Integer resolveStringMinLength(FieldScope field) {
         return this.fieldConfigPart.resolveStringMinLength(field);


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 8e02ba2..176172d 100644
--- a/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorTest.java
+++ b/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorTest.java
@@ -29,8 +29,10 @@ import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Scanner;
 import junitparams.JUnitParamsRunner;
@@ -151,24 +153,25 @@ public class SchemaGeneratorTest {
 
     private static void populateTypeConfigPart(SchemaGeneratorTypeConfigPart<?> configPart, String descriptionPrefix) {
         configPart
-                .withArrayMinItemsResolver(member -> member.isContainerType() ? 2 : null)
-                .withArrayMaxItemsResolver(member -> member.isContainerType() ? 100 : null)
-                .withArrayUniqueItemsResolver(member -> member.isContainerType() ? false : null)
-                .withDefaultResolver(member -> member.getType().isInstanceOf(Number.class) ? 1 : null)
-                .withDescriptionResolver(member -> descriptionPrefix + member.getSimpleTypeDescription())
-                .withEnumResolver(member -> member.getType().isInstanceOf(Number.class) ? Arrays.asList(1, 2, 3, 4, 5) : null)
-                .withEnumResolver(member -> member.getType().isInstanceOf(String.class) ? Arrays.asList("constant string value") : null)
+                .withArrayMinItemsResolver(scope -> scope.isContainerType() ? 2 : null)
+                .withArrayMaxItemsResolver(scope -> scope.isContainerType() ? 100 : null)
+                .withArrayUniqueItemsResolver(scope -> scope.isContainerType() ? false : null)
+                .withDefaultResolver(scope -> scope.getType().isInstanceOf(Number.class) ? 1 : null)
+                .withDescriptionResolver(scope -> descriptionPrefix + scope.getSimpleTypeDescription())
+                .withEnumResolver(scope -> scope.getType().isInstanceOf(Number.class) ? Arrays.asList(1, 2, 3, 4, 5) : null)
+                .withEnumResolver(scope -> scope.getType().isInstanceOf(String.class) ? Arrays.asList("constant string value") : null)
                 .withAdditionalPropertiesResolver(SchemaGeneratorTest::resolveAdditionalProperties)
-                .withNumberExclusiveMaximumResolver(member -> member.getType().isInstanceOf(Number.class) ? BigDecimal.TEN.add(BigDecimal.ONE) : null)
-                .withNumberExclusiveMinimumResolver(member -> member.getType().isInstanceOf(Number.class) ? BigDecimal.ZERO : null)
-                .withNumberInclusiveMaximumResolver(member -> member.getType().isInstanceOf(Number.class) ? BigDecimal.TEN : null)
-                .withNumberInclusiveMinimumResolver(member -> member.getType().isInstanceOf(Number.class) ? BigDecimal.ONE : null)
-                .withNumberMultipleOfResolver(member -> member.getType().isInstanceOf(Number.class) ? BigDecimal.ONE : null)
-                .withStringFormatResolver(member -> member.getType().isInstanceOf(String.class) ? "date" : null)
-                .withStringMaxLengthResolver(member -> member.getType().isInstanceOf(String.class) ? 256 : null)
-                .withStringMinLengthResolver(member -> member.getType().isInstanceOf(String.class) ? 1 : null)
-                .withStringPatternResolver(member -> member.getType().isInstanceOf(String.class) ? "^.{1,256}$" : null)
-                .withTitleResolver(member -> member.getSimpleTypeDescription());
+                .withPatternPropertiesResolver(SchemaGeneratorTest::resolvePatternProperties)
+                .withNumberExclusiveMaximumResolver(scope -> scope.getType().isInstanceOf(Number.class) ? BigDecimal.TEN.add(BigDecimal.ONE) : null)
+                .withNumberExclusiveMinimumResolver(scope -> scope.getType().isInstanceOf(Number.class) ? BigDecimal.ZERO : null)
+                .withNumberInclusiveMaximumResolver(scope -> scope.getType().isInstanceOf(Number.class) ? BigDecimal.TEN : null)
+                .withNumberInclusiveMinimumResolver(scope -> scope.getType().isInstanceOf(Number.class) ? BigDecimal.ONE : null)
+                .withNumberMultipleOfResolver(scope -> scope.getType().isInstanceOf(Number.class) ? BigDecimal.ONE : null)
+                .withStringFormatResolver(scope -> scope.getType().isInstanceOf(String.class) ? "date" : null)
+                .withStringMaxLengthResolver(scope -> scope.getType().isInstanceOf(String.class) ? 256 : null)
+                .withStringMinLengthResolver(scope -> scope.getType().isInstanceOf(String.class) ? 1 : null)
+                .withStringPatternResolver(scope -> scope.getType().isInstanceOf(String.class) ? "^.{1,256}$" : null)
+                .withTitleResolver(scope -> scope.getSimpleTypeDescription());
     }
 
     private static Type resolveAdditionalProperties(TypeScope scope) {
@@ -182,6 +185,15 @@ public class SchemaGeneratorTest {
         return Void.class;
     }
 
+    private static Map<String, Type> resolvePatternProperties(TypeScope scope) {
+        if (!scope.getType().isInstanceOf(TestClass2.class)) {
+            return null;
+        }
+        Map<String, Type> patternProperties = new HashMap<>();
+        patternProperties.put("^generic.+$", scope.getTypeParameterFor(TestClass2.class, 0));
+        return patternProperties;
+    }
+
     private static void populateConfigPart(SchemaGeneratorConfigPart<? extends MemberScope<?, ?>> configPart, String descriptionPrefix) {
         populateTypeConfigPart(configPart, descriptionPrefix);
         configPart
diff --git a/src/test/resources/com/github/victools/jsonschema/generator/testclass1-FULL_DOCUMENTATION-typeattributes.json b/src/test/resources/com/github/victools/jsonschema/generator/testclass1-FULL_DOCUMENTATION-typeattributes.json
index 8c4d36c..63c03ef 100644
--- a/src/test/resources/com/github/victools/jsonschema/generator/testclass1-FULL_DOCUMENTATION-typeattributes.json
+++ b/src/test/resources/com/github/victools/jsonschema/generator/testclass1-FULL_DOCUMENTATION-typeattributes.json
@@ -45,5 +45,17 @@
     },
     "title": "TestClass1",
     "description": "for type in general: TestClass1",
-    "additionalProperties": false
+    "additionalProperties": false,
+    "patternProperties": {
+        "^generic.+$": {
+            "type": "string",
+            "title": "String",
+            "description": "for type in general: String",
+            "const": "constant string value",
+            "minLength": 1,
+            "maxLength": 256,
+            "format": "date",
+            "pattern": "^.{1,256}$"
+        }
+    }
 }
diff --git a/src/test/resources/com/github/victools/jsonschema/generator/testclass3-FULL_DOCUMENTATION-typeattributes.json b/src/test/resources/com/github/victools/jsonschema/generator/testclass3-FULL_DOCUMENTATION-typeattributes.json
index 53ac95f..72e6bbc 100644
--- a/src/test/resources/com/github/victools/jsonschema/generator/testclass3-FULL_DOCUMENTATION-typeattributes.json
+++ b/src/test/resources/com/github/victools/jsonschema/generator/testclass3-FULL_DOCUMENTATION-typeattributes.json
@@ -112,7 +112,19 @@
             },
             "title": "TestClass1",
             "description": "for type in general: TestClass1",
-            "additionalProperties": false
+            "additionalProperties": false,
+            "patternProperties": {
+                "^generic.+$": {
+                    "type": "string",
+                    "title": "String",
+                    "description": "for type in general: String",
+                    "const": "constant string value",
+                    "minLength": 1,
+                    "maxLength": 256,
+                    "format": "date",
+                    "pattern": "^.{1,256}$"
+                }
+            }
         },
         "TestClass2(Long)": {
             "type": "object",
@@ -146,7 +158,21 @@
             },
             "title": "TestClass2<Long>",
             "description": "for type in general: TestClass2<Long>",
-            "additionalProperties": false
+            "additionalProperties": false,
+            "patternProperties": {
+                "^generic.+$": {
+                    "type": "integer",
+                    "title": "Long",
+                    "description": "for type in general: Long",
+                    "default": 1,
+                    "enum": [1, 2, 3, 4, 5],
+                    "minimum": 1,
+                    "exclusiveMinimum": 0,
+                    "maximum": 1E+1,
+                    "exclusiveMaximum": 11,
+                    "multipleOf": 1
+                }
+            }
         },
         "TestClass2(Long)-nullable": {
             "oneOf": [{
@@ -185,7 +211,19 @@
             },
             "title": "TestClass2<String>",
             "description": "for type in general: TestClass2<String>",
-            "additionalProperties": false
+            "additionalProperties": false,
+            "patternProperties": {
+                "^generic.+$": {
+                    "type": "string",
+                    "title": "String",
+                    "description": "for type in general: String",
+                    "const": "constant string value",
+                    "minLength": 1,
+                    "maxLength": 256,
+                    "format": "date",
+                    "pattern": "^.{1,256}$"
+                }
+            }
         },
         "TestClass2(String)-nullable": {
             "oneOf": [{
@@ -241,7 +279,20 @@
             },
             "title": "TestClass2<TestClass1[]>",
             "description": "for type in general: TestClass2<TestClass1[]>",
-            "additionalProperties": false
+            "additionalProperties": false,
+            "patternProperties": {
+                "^generic.+$": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/TestClass1"
+                    },
+                    "title": "TestClass1[]",
+                    "description": "for type in general: TestClass1[]",
+                    "minItems": 2,
+                    "maxItems": 100,
+                    "uniqueItems": false
+                }
+            }
         },
         "TestClass2(TestClass2(String))-nullable": {
             "type": ["object", "null"],
@@ -266,7 +317,12 @@
             },
             "title": "TestClass2<TestClass2<String>>",
             "description": "for type in general: TestClass2<TestClass2<String>>",
-            "additionalProperties": false
+            "additionalProperties": false,
+            "patternProperties": {
+                "^generic.+$": {
+                    "$ref": "#/definitions/TestClass2(String)"
+                }
+            }
         },
         "TestClass4(Integer,String)-nullable": {
             "type": ["object", "null"],
diff --git a/src/test/resources/com/github/victools/jsonschema/generator/testclass3-JAVA_OBJECT-methodattributes.json b/src/test/resources/com/github/victools/jsonschema/generator/testclass3-JAVA_OBJECT-methodattributes.json
index 4ca1735..a76f572 100644
--- a/src/test/resources/com/github/victools/jsonschema/generator/testclass3-JAVA_OBJECT-methodattributes.json
+++ b/src/test/resources/com/github/victools/jsonschema/generator/testclass3-JAVA_OBJECT-methodattributes.json
@@ -204,14 +204,24 @@
                                                 }, {
                                                     "title": "TestClass2<String>",
                                                     "description": "looked-up from method: TestClass2<String>",
-                                                    "additionalProperties": false
+                                                    "additionalProperties": false,
+                                                    "patternProperties": {
+                                                        "^generic.+$": {
+                                                            "type": "string"
+                                                        }
+                                                    }
                                                 }]
                                         }
                                     }
                                 }, {
                                     "title": "TestClass2<TestClass2<String>>",
                                     "description": "looked-up from method: TestClass2<TestClass2<String>>",
-                                    "additionalProperties": false
+                                    "additionalProperties": false,
+                                    "patternProperties": {
+                                        "^generic.+$": {
+                                            "$ref": "#/definitions/TestClass2(String)"
+                                        }
+                                    }
                                 }]
                         }
                     }
@@ -251,7 +261,15 @@
                 }, {
                     "title": "TestClass2<TestClass1[]>",
                     "description": "looked-up from method: TestClass2<TestClass1[]>",
-                    "additionalProperties": false
+                    "additionalProperties": false,
+                    "patternProperties": {
+                        "^generic.+$": {
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/definitions/TestClass1"
+                            }
+                        }
+                    }
                 }]
         },
         "getNestedLong()": {
@@ -264,7 +282,12 @@
                 }, {
                     "title": "TestClass2<Long>",
                     "description": "looked-up from method: TestClass2<Long>",
-                    "additionalProperties": false
+                    "additionalProperties": false,
+                    "patternProperties": {
+                        "^generic.+$": {
+                            "type": "integer"
+                        }
+                    }
                 }]
         },
         "getNestedLongList()": {
diff --git a/src/test/resources/com/github/victools/jsonschema/generator/testclass3-PLAIN_JSON-fieldattributes.json b/src/test/resources/com/github/victools/jsonschema/generator/testclass3-PLAIN_JSON-fieldattributes.json
index d0abe4a..b152fec 100644
--- a/src/test/resources/com/github/victools/jsonschema/generator/testclass3-PLAIN_JSON-fieldattributes.json
+++ b/src/test/resources/com/github/victools/jsonschema/generator/testclass3-PLAIN_JSON-fieldattributes.json
@@ -130,14 +130,24 @@
                                                 }, {
                                                     "title": "TestClass2<String>",
                                                     "description": "looked-up from field: TestClass2<String>",
-                                                    "additionalProperties": false
+                                                    "additionalProperties": false,
+                                                    "patternProperties": {
+                                                        "^generic.+$": {
+                                                            "type": "string"
+                                                        }
+                                                    }
                                                 }]
                                         }
                                     }
                                 }, {
                                     "title": "TestClass2<TestClass2<String>>",
                                     "description": "looked-up from field: TestClass2<TestClass2<String>>",
-                                    "additionalProperties": false
+                                    "additionalProperties": false,
+                                    "patternProperties": {
+                                        "^generic.+$": {
+                                            "$ref": "#/definitions/TestClass2(String)"
+                                        }
+                                    }
                                 }]
                         },
                         "optionalS": {
@@ -194,7 +204,15 @@
                 }, {
                     "title": "TestClass2<TestClass1[]>",
                     "description": "looked-up from field: TestClass2<TestClass1[]>",
-                    "additionalProperties": false
+                    "additionalProperties": false,
+                    "patternProperties": {
+                        "^generic.+$": {
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/definitions/TestClass1"
+                            }
+                        }
+                    }
                 }]
         },
         "nestedLong": {
@@ -203,7 +221,12 @@
                 }, {
                     "title": "TestClass2<Long>",
                     "description": "looked-up from field: TestClass2<Long>",
-                    "additionalProperties": false
+                    "additionalProperties": false,
+                    "patternProperties": {
+                        "^generic.+$": {
+                            "type": "integer"
+                        }
+                    }
                 }]
         },
         "nestedLongList": {