diff --git a/README.md b/README.md
index 011d32ef..c2428660 100644
--- a/README.md
+++ b/README.md
@@ -26,8 +26,11 @@ Users are encouraged to use more up-to-date JSON Schema support tools.
(from [TestGenerateJsonSchema](https://github.com/FasterXML/jackson-module-jsonSchema/blob/master/src/test/java/com/fasterxml/jackson/module/jsonSchema/TestGenerateJsonSchema.java#L136))
-simply add a dependency (this is from my gradle config)
-`"com.fasterxml.jackson.module:jackson-module-jsonSchema:2.9.0"`
+simply add a dependency
+`"com.fasterxml.jackson.module:jackson-module-jsonSchema:2.15.0"`
+or
+`"com.fasterxml.jackson.module:jackson-module-jsonSchema-jakarta:2.15.0"` for the jakarta namespace
+
and for gradle, at least, you can simply add `mavenLocal()` to your repositories.
Maven should resolve the dependency from its local repo transparently.
diff --git a/jakarta/pom.xml b/jakarta/pom.xml
new file mode 100644
index 00000000..37047019
--- /dev/null
+++ b/jakarta/pom.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+ 4.0.0
+
+ com.fasterxml.jackson.module
+ jackson-module-jsonSchema-parent
+ 2.15.0-SNAPSHOT
+
+ jackson-module-jsonSchema-jakarta
+ jackson-module-jsonSchema-jakarta
+ bundle
+ Add-on module for Jackson (http://jackson.codehaus.org) to support
+JSON Schema (http://tools.ietf.org/html/draft-zyp-json-schema-03) version 3 generation.
+
+
+
+
+ jakarta.validation
+ jakarta.validation-api
+ 3.0.2
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${version.plugin.surefire}
+
+
+ com/fasterxml/jackson/module/jsonSchema/jakarta/failing/*.java
+
+
+
+
+
+ org.moditect
+ moditect-maven-plugin
+
+
+
+ de.jjohannes
+ gradle-module-metadata-maven-plugin
+
+
+
+
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/JsonSchema.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/JsonSchema.java
new file mode 100644
index 00000000..35d94dc9
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/JsonSchema.java
@@ -0,0 +1,577 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.AnySchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ArraySchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.BooleanSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ContainerTypeSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.IntegerSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.NullSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.NumberSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ObjectSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.SimpleTypeSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.StringSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.UnionTypeSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ValueTypeSchema;
+
+/**
+ * The type wraps the json schema specification at :
+ * Json JsonSchema
+ * Draft
JSON (JavaScript Object Notation) JsonSchema defines the
+ * media type "application/schema+json", a JSON based format for defining the
+ * structure of JSON data. JSON JsonSchema provides a contract for what JSON data is
+ * required for a given application and how to interact with it. JSON JsonSchema is
+ * intended to define validation, documentation, hyperlink navigation, and
+ * interaction control of JSON data.
+ *
+ *
JSON (JavaScript Object Notation) JsonSchema is a JSON media type
+ * for defining the structure of JSON data. JSON JsonSchema provides a contract for
+ * what JSON data is required for a given application and how to interact with
+ * it. JSON JsonSchema is intended to define validation, documentation, hyperlink
+ * navigation, and interaction control of JSON data.
+ *
+ * An example JSON JsonSchema provided by the JsonSchema draft:
+ *
+ *
+ *
+ * @author jphelan
+ */
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+@JsonTypeInfo(use = Id.CUSTOM, include = As.PROPERTY, property = "type")
+@JsonTypeIdResolver(JsonSchemaIdResolver.class)
+public abstract class JsonSchema
+{
+ /**
+ * This attribute defines the current URI of this schema (this attribute is
+ * effectively a "self" link). This URI MAY be relative or absolute. If the
+ * URI is relative it is resolved against the current URI of the parent
+ * schema it is contained in. If this schema is not contained in any parent
+ * schema, the current URI of the parent schema is held to be the URI under
+ * which this schema was addressed. If id is missing, the current URI of a
+ * schema is defined to be that of the parent schema. The current URI of the
+ * schema is also used to construct relative references such as for $ref.
+ */
+ @JsonProperty
+ private String id;
+
+ /**
+ * This attribute defines a URI of a schema that contains the full
+ * representation of this schema. When a validator encounters this
+ * attribute, it SHOULD replace the current schema with the schema
+ * referenced by the value's URI (if known and available) and re- validate
+ * the instance. This URI MAY be relative or absolute, and relative URIs
+ * SHOULD be resolved against the URI of the current schema.
+ */
+ @JsonProperty
+ private String $ref;
+
+ /**
+ * This attribute defines a URI of a JSON JsonSchema that is the schema of the
+ * current schema. When this attribute is defined, a validator SHOULD use
+ * the schema referenced by the value's URI (if known and available) when
+ * resolving Hyper JsonSchema (Section 6) links (Section 6.1).
+ *
+ * A validator MAY use this attribute's value to determine which version of
+ * JSON JsonSchema the current schema is written in, and provide the appropriate
+ * validation features and behavior. Therefore, it is RECOMMENDED that all
+ * schema authors include this attribute in their schemas to prevent
+ * conflicts with future JSON JsonSchema specification changes.
+ */
+ @JsonProperty
+ private String $schema;
+
+ /**
+ * This attribute takes the same values as the "type" attribute, however if
+ * the instance matches the type or if this value is an array and the
+ * instance matches any type or schema in the array, then this instance is
+ * not valid.
+ */
+ @JsonProperty
+ private JsonSchema[] disallow;
+
+ /**
+ * The value of this property MUST be another schema which will provide a
+ * base schema which the current schema will inherit from. The inheritance
+ * rules are such that any instance that is valid according to the current
+ * schema MUST be valid according to the referenced schema. This MAY also be
+ * an array, in which case, the instance MUST be valid for all the schemas
+ * in the array. A schema that extends another schema MAY define additional
+ * attributes, constrain existing attributes, or add other constraints.
+ *
+ * Conceptually, the behavior of extends can be seen as validating an
+ * instance against all constraints in the extending schema as well as the
+ * extended schema(s). More optimized implementations that merge schemas are
+ * possible, but are not required. An example of using "extends":
+ *
+ * { "description":"An adult", "properties":{"age":{"minimum": 21}},
+ * "extends":"person" } { "description":"Extended schema",
+ * "properties":{"deprecated":{"type": "boolean"}},
+ * "extends":"http://json-schema.org/draft-03/schema" }
+ */
+ private JsonSchema[] extendsextends;
+
+ /**
+ * This attribute indicates if the instance must have a value, and not be
+ * undefined. This is false by default, making the instance optional.
+ */
+ @JsonProperty
+ private Boolean required = null;
+
+ /**
+ * This attribute indicates if the instance is not modifiable.
+ * This is false by default, making the instance modifiable.
+ */
+ @JsonProperty
+ private Boolean readonly = null;
+
+ /**
+ * This attribute is a string that provides a full description of the of
+ * purpose the instance property.
+ */
+ private String description;
+
+ protected JsonSchema() { }
+
+ /**
+ * Attempt to return this JsonSchema as an {@link AnySchema}
+ * @return this as an AnySchema if possible, or null otherwise
+ */
+ public AnySchema asAnySchema() {
+ return null;
+ }
+
+ /**
+ * Attempt to return this JsonSchema as an {@link ArraySchema}
+ * @return this as an ArraySchema if possible, or null otherwise
+ */
+ public ArraySchema asArraySchema() {
+ return null;
+ }
+
+ /**
+ * Attempt to return this JsonSchema as a {@link BooleanSchema}
+ * @return this as a BooleanSchema if possible, or null otherwise
+ */
+ public BooleanSchema asBooleanSchema() {
+ return null;
+ }
+
+ /**
+ * @deprecated Since 2.7
+ */
+ @Deprecated
+ public ContainerTypeSchema asContainerSchema() {
+ return asContainerTypeSchema();
+ }
+
+ /**
+ * Attempt to return this JsonSchema as a {@link ContainerTypeSchema}
+ * @return this as an ContainerTypeSchema if possible, or null otherwise
+ *
+ * @since 2.7
+ */
+ public ContainerTypeSchema asContainerTypeSchema() {
+ return null;
+ }
+
+ /**
+ * Attempt to return this JsonSchema as an {@link IntegerSchema}
+ * @return this as an IntegerSchema if possible, or null otherwise
+ */
+ public IntegerSchema asIntegerSchema() {
+ return null;
+ }
+
+ /**
+ * Attempt to return this JsonSchema as a {@link NullSchema}
+ * @return this as a NullSchema if possible, or null otherwise
+ */
+ public NullSchema asNullSchema() {
+ return null;
+ }
+
+ /**
+ * Attempt to return this JsonSchema as a {@link NumberSchema}
+ * @return this as a NumberSchema if possible, or null otherwise
+ */
+ public NumberSchema asNumberSchema() {
+ return null;
+ }
+
+ /**
+ * Attempt to return this JsonSchema as an {@link ObjectSchema}
+ * @return this as an ObjectSchema if possible, or null otherwise
+ */
+ public ObjectSchema asObjectSchema() {
+ return null;
+ }
+
+ /**
+ * Attempt to return this JsonSchema as a {@link SimpleTypeSchema}
+ * @return this as a SimpleTypeSchema if possible, or null otherwise
+ */
+ public SimpleTypeSchema asSimpleTypeSchema() {
+ return null;
+ }
+
+ /**
+ * Attempt to return this JsonSchema as a {@link StringSchema}
+ * @return this as a StringSchema if possible, or null otherwise
+ */
+ public StringSchema asStringSchema() {
+ return null;
+ }
+
+ /**
+ * Attempt to return this JsonSchema as an {@link UnionTypeSchema}
+ * @return this as a UnionTypeSchema if possible, or null otherwise
+ */
+ public UnionTypeSchema asUnionTypeSchema() {
+ return null;
+ }
+
+ /**
+ * @deprecated Since 2.7
+ */
+ @Deprecated
+ public ValueTypeSchema asValueSchemaSchema() {
+ return asValueTypeSchema();
+ }
+
+ /**
+ * Attempt to return this JsonSchema as a {@link ValueTypeSchema}
+ * @return this as a ValueTypeSchema if possible, or null otherwise
+ *
+ * @since 2.7
+ */
+ public ValueTypeSchema asValueTypeSchema() {
+ return null;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String get$ref() {
+ return $ref;
+ }
+
+ public String get$schema() {
+ return $schema;
+ }
+
+ public JsonSchema[] getDisallow() {
+ return disallow;
+ }
+
+ public JsonSchema[] getExtends() {
+ return extendsextends;
+ }
+
+ public Boolean getRequired() {
+ return required;
+ }
+
+ public Boolean getReadonly() {
+ return readonly;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ @JsonIgnore
+ public abstract JsonFormatTypes getType();
+
+ /**
+ * determine if this JsonSchema is an {@link AnySchema}.
+ *
+ * @return true if this JsonSchema is an AnySchema, false otherwise
+ */
+ @JsonIgnore
+ public boolean isAnySchema() {
+ return false;
+ }
+
+ /**
+ * determine if this JsonSchema is an {@link ArraySchema}.
+ *
+ * @return true if this JsonSchema is an ArraySchema, false otherwise
+ */
+ @JsonIgnore
+ public boolean isArraySchema() {
+ return false;
+ }
+
+ /**
+ * determine if this JsonSchema is an {@link BooleanSchema}.
+ *
+ * @return true if this JsonSchema is an BooleanSchema, false otherwise
+ */
+ @JsonIgnore
+ public boolean isBooleanSchema() {
+ return false;
+ }
+
+ /**
+ * determine if this JsonSchema is an {@link ContainerTypeSchema}.
+ *
+ * @return true if this JsonSchema is an ContainerTypeSchema, false otherwise
+ */
+ @JsonIgnore
+ public boolean isContainerTypeSchema() {
+ return false;
+ }
+
+ /**
+ * determine if this JsonSchema is an {@link IntegerSchema}.
+ *
+ * @return true if this JsonSchema is an IntegerSchema, false otherwise
+ */
+ @JsonIgnore
+ public boolean isIntegerSchema() {
+ return false;
+ }
+
+ /**
+ * determine if this JsonSchema is an {@link NullSchema}.
+ *
+ * @return true if this JsonSchema is an NullSchema, false otherwise
+ */
+ @JsonIgnore
+ public boolean isNullSchema() {
+ return false;
+ }
+
+ /**
+ * determine if this JsonSchema is an {@link NumberSchema}.
+ *
+ * @return true if this JsonSchema is an NumberSchema, false otherwise
+ */
+ @JsonIgnore
+ public boolean isNumberSchema() {
+ return false;
+ }
+
+ /**
+ * determine if this JsonSchema is an {@link ObjectSchema}.
+ *
+ * @return true if this JsonSchema is an ObjectSchema, false otherwise
+ */
+ @JsonIgnore
+ public boolean isObjectSchema() {
+ return false;
+ }
+
+ /**
+ * determine if this JsonSchema is an {@link SimpleTypeSchema}.
+ *
+ * @return true if this JsonSchema is an SimpleTypeSchema, false otherwise
+ */
+ @JsonIgnore
+ public boolean isSimpleTypeSchema() {
+ return false;
+ }
+
+ /**
+ * determine if this JsonSchema is an {@link StringSchema}.
+ *
+ * @return true if this JsonSchema is an StringSchema, false otherwise
+ */
+ @JsonIgnore
+ public boolean isStringSchema() {
+ return false;
+ }
+
+ /**
+ * determine if this JsonSchema is an {@link UnionTypeSchema}.
+ *
+ * @return true if this JsonSchema is an UnionTypeSchema, false otherwise
+ */
+ @JsonIgnore
+ public boolean isUnionTypeSchema() {
+ return false;
+ }
+
+ /**
+ * determine if this JsonSchema is an {@link ValueTypeSchema}.
+ *
+ * @return true if this JsonSchema is an ValueTypeSchema, false otherwise
+ */
+ @JsonIgnore
+ public boolean isValueTypeSchema() {
+ return false;
+ }
+
+ public void set$ref(String $ref) {
+ this.$ref = $ref;
+ }
+
+ public void set$schema(String $schema) {
+ this.$schema = $schema;
+ }
+
+ public void setDisallow(JsonSchema[] disallow) {
+ this.disallow = disallow;
+ }
+
+ public void setExtends(JsonSchema[] extendsextends) {
+ this.extendsextends = extendsextends;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public void setRequired(Boolean required) {
+ this.required = required;
+ }
+
+ public void setReadonly(Boolean readonly){
+ this.readonly = readonly;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ /**
+ * Override this to add information specific to the property of bean
+ * For example, bean validation annotations could be used to specify
+ * value constraints in the schema
+ * @param beanProperty
+ */
+ public void enrichWithBeanProperty(BeanProperty beanProperty) {
+ setDescription(beanProperty.getMetadata().getDescription());
+ }
+
+ /**
+ * Create a schema which verifies only that an object is of the given format.
+ * @param format the format to expect
+ * @return the schema verifying the given format
+ */
+ public static JsonSchema minimalForFormat(JsonFormatTypes format)
+ {
+ if (format != null) {
+ switch (format) {
+ case ARRAY:
+ return new ArraySchema();
+ case OBJECT:
+ return new ObjectSchema();
+ case BOOLEAN:
+ return new BooleanSchema();
+ case INTEGER:
+ return new IntegerSchema();
+ case NUMBER:
+ return new NumberSchema();
+ case STRING:
+ return new StringSchema();
+ case NULL:
+ return new NullSchema();
+ case ANY:
+ default:
+ }
+ }
+ return new AnySchema();
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == this) return true;
+ if (obj == null) return false;
+ if (!(obj instanceof JsonSchema)) return false;
+ return _equals((JsonSchema) obj);
+ }
+
+ protected boolean _equals(JsonSchema that)
+ {
+ return equals(getId(), that.getId())
+ // 27-Apr-2015, tatu: Should not need to check type explicitly
+ // && equals(getType(), getType())
+ && equals(getRequired(), that.getRequired())
+ && equals(getReadonly(), that.getReadonly())
+ && equals(get$ref(), that.get$ref())
+ && equals(get$schema(), that.get$schema())
+ && arraysEqual(getDisallow(), that.getDisallow())
+ && arraysEqual(getExtends(), that.getExtends());
+ }
+
+ /**
+ * A utility method allowing to easily chain calls to equals() on members
+ * without taking any risk regarding the ternary operator precedence.
+ *
+ * @return (object1 == null ? object2 == null : object1.equals(object2))
+ */
+ protected static boolean equals(Object object1, Object object2) {
+ if (object1 == null) {
+ return object2 == null;
+ }
+ return object1.equals(object2);
+ }
+
+ protected static boolean arraysEqual(T[] arr1, T[] arr2) {
+ if (arr1 == null) {
+ return arr2 == null;
+ }
+ if (arr2 == null) {
+ return false;
+ }
+ final int len = arr1.length;
+ if (len != arr2.length) {
+ return false;
+ }
+ for (int i = 0; i < len; ++i) {
+ if (!equals(arr1[i], arr2[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/JsonSchemaGenerator.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/JsonSchemaGenerator.java
new file mode 100644
index 00000000..9c64e8e5
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/JsonSchemaGenerator.java
@@ -0,0 +1,102 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.SchemaFactoryWrapper;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.WrapperFactory;
+
+/**
+ * Convenience class that wraps JSON Schema generation functionality.
+ *
+ * @author tsaloranta
+ */
+public class JsonSchemaGenerator
+{
+ /**
+ * @deprecated Since 2.6
+ */
+ @Deprecated
+ protected final ObjectMapper _mapper;
+
+ /**
+ * @since 2.6
+ */
+ protected final ObjectWriter _writer;
+
+ private final WrapperFactory _wrapperFactory;
+
+ /**
+ * @since 2.8.1
+ */
+ private final SchemaFactoryWrapper _visitor;
+
+ public JsonSchemaGenerator(ObjectMapper mapper) {
+ this(mapper, (WrapperFactory) null);
+ }
+
+ public JsonSchemaGenerator(ObjectMapper mapper, WrapperFactory wrapperFactory) {
+ _mapper = mapper;
+ _writer = mapper.writer();
+ _wrapperFactory = (wrapperFactory == null) ? new WrapperFactory() : wrapperFactory;
+ _visitor = null;
+ }
+
+ /**
+ * NOTE: resulting generator is NOT thread-safe, since typically {@link SchemaFactoryWrapper}
+ * being passed is not thread-safe.
+ *
+ * @since 2.8.1
+ */
+ public JsonSchemaGenerator(ObjectMapper mapper, SchemaFactoryWrapper visitor) {
+ this(mapper.writer(), visitor);
+ }
+
+ /**
+ * @since 2.6
+ */
+ public JsonSchemaGenerator(ObjectWriter w) {
+ this(w, (WrapperFactory) null);
+ }
+
+ /**
+ * @since 2.6
+ */
+ public JsonSchemaGenerator(ObjectWriter w, WrapperFactory wrapperFactory) {
+ _mapper = null;
+ _writer = w;
+ _wrapperFactory = (wrapperFactory == null) ? new WrapperFactory() : wrapperFactory;
+ _visitor = null;
+ }
+
+ /**
+ * @since 2.8.1
+ */
+ public JsonSchemaGenerator(ObjectWriter w, SchemaFactoryWrapper visitor) {
+ _mapper = null;
+ _writer = w;
+ _wrapperFactory = null;
+ if (visitor == null) {
+ throw new IllegalArgumentException("Missing `visitor`");
+ }
+ _visitor = visitor;
+ }
+
+ public JsonSchema generateSchema(Class> type) throws JsonMappingException
+ {
+ SchemaFactoryWrapper visitor = _visitor;
+ if (visitor == null) {
+ visitor = _wrapperFactory.getWrapper(null);
+ }
+ _writer.acceptJsonFormatVisitor(type, visitor);
+ return visitor.finalSchema();
+ }
+
+ public JsonSchema generateSchema(JavaType type) throws JsonMappingException
+ {
+ SchemaFactoryWrapper visitor = _visitor;
+ if (visitor == null) {
+ visitor = _wrapperFactory.getWrapper(null);
+ }
+ _writer.acceptJsonFormatVisitor(type, visitor);
+ return visitor.finalSchema();
+ }
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/JsonSchemaIdResolver.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/JsonSchemaIdResolver.java
new file mode 100644
index 00000000..4539dd73
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/JsonSchemaIdResolver.java
@@ -0,0 +1,86 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+import com.fasterxml.jackson.databind.DatabindContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes;
+import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.AnySchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ArraySchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.BooleanSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.IntegerSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.NullSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.NumberSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ObjectSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.StringSchema;
+import java.util.Arrays;
+
+/**
+ * Type id resolver needed to support polymorphic (de)serialization of all kinds of
+ * {@link JsonSchema} instances.
+ * Note that to support custom types, you will need to sub-class this resolver
+ * and override at least {@link #idFromValue(Object)}, {@link #idFromValueAndType(Object, Class)} and
+ * {@link #typeFromId} methods; as well as associate this resolver using
+ * {@link com.fasterxml.jackson.annotation.JsonTypeInfo} annotation on
+ * all custom {@link JsonSchema} implementation classes.
+ */
+public class JsonSchemaIdResolver extends TypeIdResolverBase
+{
+ public JsonSchemaIdResolver() { }
+
+ @Override
+ public String idFromValue(Object value) {
+ if (value instanceof JsonSchema) {
+ return ((JsonSchema)value).getType().value();
+ }
+ return null;
+ }
+
+ @Override
+ public String idFromValueAndType(Object value, Class> suggestedType) {
+ return idFromValue(value);
+ }
+
+ @Override
+ public JavaType typeFromId(DatabindContext ctxt, String id)
+ {
+ JsonFormatTypes stdType = JsonFormatTypes.forValue(id);
+ if (stdType != null) {
+ switch (stdType) {
+ case ARRAY:
+ return ctxt.constructType(ArraySchema.class);
+ case BOOLEAN:
+ return ctxt.constructType(BooleanSchema.class);
+ case INTEGER:
+ return ctxt.constructType(IntegerSchema.class);
+ case NULL:
+ return ctxt.constructType(NullSchema.class);
+ case NUMBER:
+ return ctxt.constructType(NumberSchema.class);
+ case OBJECT:
+ return ctxt.constructType(ObjectSchema.class);
+ case STRING:
+ return ctxt.constructType(StringSchema.class);
+ case ANY:
+ default:
+ return ctxt.constructType(AnySchema.class);
+ }
+ }
+ // Not a standard type; should use a custom sub-type impl
+ throw new IllegalArgumentException("Can not resolve JsonSchema 'type' id of \""+id
+ +"\", not recognized as one of standard values: "+Arrays.asList(JsonFormatTypes.values()));
+ }
+
+ @Override
+ public Id getMechanism() {
+ return Id.CUSTOM;
+ }
+
+ @Override
+ public void init(JavaType baseType) { }
+
+ @Override
+ public String idFromBaseType() {
+ return null;
+ }
+ }
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/annotation/JsonHyperSchema.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/annotation/JsonHyperSchema.java
new file mode 100644
index 00000000..3ba65033
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/annotation/JsonHyperSchema.java
@@ -0,0 +1,95 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * Created by mavarazy on 4/21/14.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+public @interface JsonHyperSchema {
+
+ /**
+ * This attribute is a URI that defines what the instance's URI MUST
+ start with in order to validate. The value of the "pathStart"
+ attribute MUST be resolved as per RFC 3986, Sec 5 [RFC3986], and is
+ relative to the instance's URI.
+
+ When multiple schemas have been referenced for an instance, the user
+ agent can determine if this jsonSchema is applicable for a particular
+ instance by determining if the URI of the instance begins with the
+ the value of the "pathStart" attribute. If the URI of the instance
+ does not start with this URI, or if another jsonSchema specifies a
+ starting URI that is longer and also matches the instance, this
+ jsonSchema SHOULD NOT be applied to the instance. Any jsonSchema that does
+ not have a pathStart attribute SHOULD be considered applicable to all
+ the instances for which it is referenced.
+ */
+ String pathStart() default "";
+
+ /**
+ * 6.2.1. slash-delimited fragment resolution
+
+ With the slash-delimited fragment resolution protocol, the fragment
+ identifier is interpreted as a series of property reference tokens
+ that start with and are delimited by the "/" character (\x2F). Each
+ property reference token is a series of unreserved or escaped URI
+ characters. Each property reference token SHOULD be interpreted,
+ starting from the beginning of the fragment identifier, as a path
+ reference in the target JSON structure. The final target value of
+ the fragment can be determined by starting with the root of the JSON
+ structure from the representation of the resource identified by the
+ pre-fragment URI. If the target is a JSON object, then the new
+ target is the value of the property with the name identified by the
+ next property reference token in the fragment. If the target is a
+ JSON array, then the target is determined by finding the item in
+ array the array with the index defined by the next property reference
+ token (which MUST be a number). The target is successively updated
+ for each property reference token, until the entire fragment has been
+ traversed.
+
+ Property names SHOULD be URI-encoded. In particular, any "/" in a
+ property name MUST be encoded to avoid being interpreted as a
+ property delimiter.
+
+ For example, for the following JSON representation:
+
+ {
+ "foo":{
+ "anArray":[
+ {"prop":44}
+ ],
+ "another prop":{
+ "baz":"A string"
+ }
+ }
+ }
+
+ The following fragment identifiers would be resolved:
+
+ fragment identifier resolution
+ ------------------- ----------
+ # self, the root of the resource itself
+ #/foo the object referred to by the foo property
+ #/foo/another%20prop the object referred to by the "another prop"
+ property of the object referred to by the
+ "foo" property
+ #/foo/another%20prop/baz the string referred to by the value of "baz"
+ property of the "another prop" property of
+ the object referred to by the "foo" property
+ #/foo/anArray/0 the first object in the "anArray" array
+
+ 6.2.2. dot-delimited fragment resolution
+
+ The dot-delimited fragment resolution protocol is the same as slash-
+ delimited fragment resolution protocol except that the "." character
+ (\x2E) is used as the delimiter between property names (instead of
+ "/") and the path does not need to start with a ".". For example,
+ #.foo and #foo are a valid fragment identifiers for referencing the
+ value of the foo propery.
+ */
+
+ Link[] links() default {};
+
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/annotation/Link.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/annotation/Link.java
new file mode 100644
index 00000000..3ca03db9
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/annotation/Link.java
@@ -0,0 +1,209 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.annotation;
+
+/**
+ * A link description object is used to describe link relations. In the
+ context of a jsonSchema, it defines the link relations of the instances
+ of the jsonSchema, and can be parameterized by the instance values. The
+ link description format can be used on its own in regular (non-jsonSchema
+ documents), and use of this format can be declared by referencing the
+ normative link description jsonSchema as the the jsonSchema for the data
+ structure that uses the links.
+ */
+public @interface Link {
+
+ /**
+ * The value of the "href" link description property indicates the
+ target URI of the related resource. The value of the instance
+ property SHOULD be resolved as a URI-Reference per RFC 3986 [RFC3986]
+ and MAY be a relative URI. The base URI to be used for relative
+ resolution SHOULD be the URI used to retrieve the instance object
+ (not the jsonSchema) when used within a jsonSchema. Also, when links are
+ used within a jsonSchema, the URI SHOULD be parametrized by the property
+ values of the instance object, if property values exist for the
+ corresponding variables in the template (otherwise they MAY be
+ provided from alternate sources, like user input).
+
+ Instance property values SHOULD be substituted into the URIs where
+ matching braces ('{', '}') are found surrounding zero or more
+ characters, creating an expanded URI. Instance property value
+ substitutions are resolved by using the text between the braces to
+ denote the property name from the instance to get the value to
+ substitute. For example, if an href value is defined:
+
+ http://somesite.com/{id}
+
+ Then it would be resolved by replace the value of the "id" property
+ value from the instance object. If the value of the "id" property
+ was "45", the expanded URI would be:
+
+ http://somesite.com/45
+
+ If matching braces are found with the string "@" (no quotes) between
+ the braces, then the actual instance value SHOULD be used to replace
+ the braces, rather than a property value. This should only be used
+ in situations where the instance is a scalar (string, boolean, or
+ number), and not for objects or arrays.
+
+ */
+ String href();
+
+ /**
+ * The value of the "rel" property indicates the name of the relation to
+ the target resource. The relation to the target SHOULD be
+ interpreted as specifically from the instance object that the jsonSchema
+ (or sub-jsonSchema) applies to, not just the top level resource that
+ contains the object within its hierarchy. If a resource JSON
+ representation contains a sub object with a property interpreted as a
+ link, that sub-object holds the relation with the target. A relation
+ to target from the top level resource MUST be indicated with the
+ jsonSchema describing the top level JSON representation.
+
+ Relationship definitions SHOULD NOT be media type dependent, and
+ users are encouraged to utilize existing accepted relation
+ definitions, including those in existing relation registries (see RFC
+ 4287 [RFC4287]). However, we define these relations here for clarity
+ of normative interpretation within the context of JSON hyper jsonSchema
+ defined relations:
+
+ self If the relation value is "self", when this property is
+ encountered in the instance object, the object represents a
+ resource and the instance object is treated as a full
+ representation of the target resource identified by the specified
+ URI.
+
+ full This indicates that the target of the link is the full
+ representation for the instance object. The object that contains
+ this link possibly may not be the full representation.
+
+ describedby This indicates the target of the link is the jsonSchema for
+ the instance object. This MAY be used to specifically denote the
+ schemas of objects within a JSON object hierarchy, facilitating
+ polymorphic type data structures.
+
+ root This relation indicates that the target of the link SHOULD be
+ treated as the root or the body of the representation for the
+ purposes of user agent interaction or fragment resolution. All
+ other properties of the instance objects can be regarded as meta-
+ data descriptions for the data.
+
+ The following relations are applicable for schemas (the jsonSchema as the
+ "from" resource in the relation):
+
+ instances This indicates the target resource that represents
+ collection of instances of a jsonSchema.
+
+ create This indicates a target to use for creating new instances of
+ a jsonSchema. This link definition SHOULD be a submission link with a
+ non-safe method (like POST).
+
+ For example, if a jsonSchema is defined:
+
+ {
+ "links": [
+ {
+ "rel": "self"
+ "href": "{id}"
+ },
+ {
+ "rel": "up"
+ "href": "{upId}"
+ },
+ {
+ "rel": "children"
+ "href": "?upId={id}"
+ }
+ ]
+ }
+
+ And if a collection of instance resource's JSON representation was
+ retrieved:
+
+ GET /Resource/
+
+ [
+ {
+ "id": "thing",
+ "upId": "parent"
+ },
+ {
+ "id": "thing2",
+ "upId": "parent"
+ }
+ ]
+
+ This would indicate that for the first item in the collection, its
+ own (self) URI would resolve to "/Resource/thing" and the first
+ item's "up" relation SHOULD be resolved to the resource at
+ "/Resource/parent". The "children" collection would be located at
+ "/Resource/?upId=thing".
+ */
+ String rel();
+
+
+ /**
+ * This property value is a jsonSchema that defines the expected structure
+ of the JSON representation of the target of the link.
+ */
+ Class> targetSchema() default void.class;
+
+ /**
+ * This attribute defines which method can be used to access the target
+ resource. In an HTTP environment, this would be "GET" or "POST"
+ (other HTTP methods such as "PUT" and "DELETE" have semantics that
+ are clearly implied by accessed resources, and do not need to be
+ defined here). This defaults to "GET".
+ */
+ String method() default "GET";
+
+ /**
+ * If present, this property indicates a query media type format that
+ the server supports for querying or posting to the collection of
+ instances at the target resource. The query can be suffixed to the
+ target URI to query the collection with property-based constraints on
+ the resources that SHOULD be returned from the server or used to post
+ data to the resource (depending on the method). For example, with
+ the following jsonSchema:
+
+ {
+ "links":[
+ {
+ "enctype":"application/x-www-form-urlencoded",
+ "method":"GET",
+ "href":"/Product/",
+ "properties":{
+ "name":{"description":"name of the product"}
+ }
+ }
+ ]
+ }
+ This indicates that the client can query the server for instances
+ that have a specific name:
+
+ /Product/?name=Slinky
+
+ If no enctype or method is specified, only the single URI specified
+ by the href property is defined. If the method is POST,
+ "application/json" is the default media type.
+ */
+ String enctype() default "application/json";
+
+ /**
+ * This attribute contains a jsonSchema which defines the acceptable
+ structure of the submitted request (for a GET request, this jsonSchema
+ would define the properties for the query string and for a POST
+ request, this would define the body).
+ */
+ Class> schema() default void.class;
+
+ /**
+ * This property defines a title for the link. The value must be a string.
+ User agents MAY use this title when presenting the link to the user.
+ */
+ String title() default "";
+
+ /**
+ * The value of this property is advisory only, and represents the media type RFC 2046 [RFC2046],
+ that is expected to be returned when fetching this resource.
+ */
+ String mediaType() default "application/json";
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/customProperties/HyperSchemaFactoryWrapper.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/customProperties/HyperSchemaFactoryWrapper.java
new file mode 100644
index 00000000..47931270
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/customProperties/HyperSchemaFactoryWrapper.java
@@ -0,0 +1,134 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.customProperties;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
+import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.JsonSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.ArrayVisitor;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.ObjectVisitor;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.SchemaFactoryWrapper;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.VisitorContext;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.WrapperFactory;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.LinkDescriptionObject;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ReferenceSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.SimpleTypeSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.annotation.JsonHyperSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.annotation.Link;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.*;
+
+/**
+ * Adds a hyperlink to object schema, either root level or nested. Generally
+ * useful for writing additional properties to a schema.
+ *
+ * @author mavarazy
+ */
+public class HyperSchemaFactoryWrapper extends SchemaFactoryWrapper {
+
+ private boolean ignoreDefaults = true;
+
+ private static class HyperSchemaFactoryWrapperFactory extends WrapperFactory {
+ @Override
+ public SchemaFactoryWrapper getWrapper(SerializerProvider p) {
+ return new HyperSchemaFactoryWrapper(p);
+ };
+
+ @Override
+ public SchemaFactoryWrapper getWrapper(SerializerProvider p, VisitorContext rvc)
+ {
+ return new HyperSchemaFactoryWrapper(p)
+ .setVisitorContext(rvc);
+ }
+ };
+
+ public HyperSchemaFactoryWrapper() {
+ super(new HyperSchemaFactoryWrapperFactory());
+ }
+
+ public HyperSchemaFactoryWrapper(SerializerProvider p) {
+ super(p, new HyperSchemaFactoryWrapperFactory());
+ }
+
+ @Override
+ public JsonObjectFormatVisitor expectObjectFormat(JavaType convertedType) {
+ ObjectVisitor visitor = ((ObjectVisitor)super.expectObjectFormat(convertedType));
+
+ // could add other properties here
+ addHyperlinks(visitor.getSchema(), convertedType);
+
+ return visitor;
+ }
+
+ @Override
+ public JsonArrayFormatVisitor expectArrayFormat(JavaType convertedType) {
+ ArrayVisitor visitor = ((ArrayVisitor)super.expectArrayFormat(convertedType));
+
+ // could add other properties here
+ addHyperlinks(visitor.getSchema(), convertedType);
+
+ return visitor;
+ }
+
+ public void setIgnoreDefaults(boolean ignoreDefaults) {
+ this.ignoreDefaults = ignoreDefaults;
+ }
+
+ /**
+ * Adds writes the type as the title of the schema.
+ *
+ * @param schema The schema who's title to set.
+ * @param type The type of the object represented by the schema.
+ */
+ private void addHyperlinks(JsonSchema schema, JavaType type) {
+ if (!schema.isSimpleTypeSchema()) {
+ throw new RuntimeException("given non simple type schema: " + schema.getType());
+ }
+ Class> rawClass = type.getRawClass();
+ if (rawClass.isAnnotationPresent(JsonHyperSchema.class)) {
+ JsonHyperSchema hyperSchema = rawClass.getAnnotation(JsonHyperSchema.class);
+ String pathStart = hyperSchema.pathStart();
+ Link[] links = hyperSchema.links();
+ LinkDescriptionObject[] linkDescriptionObjects = new LinkDescriptionObject[links.length];
+ for(int i = 0; i < links.length; i++) {
+ Link link = links[i];
+ linkDescriptionObjects[i] = new LinkDescriptionObject()
+ .setHref(pathStart + link.href())
+ .setRel(link.rel())
+ .setMethod(ignoreDefaults && "GET".equals(link.method()) ? null : link.method())
+ .setEnctype(ignoreDefaults && "application/json".equals(link.enctype()) ? null : link.enctype())
+ .setTargetSchema(fetchSchema(link.targetSchema()))
+ .setSchema(fetchSchema(link.schema()))
+ .setMediaType(ignoreDefaults && "application/json".equals(link.mediaType()) ? null : link.mediaType())
+ .setTitle(link.title());
+ }
+ SimpleTypeSchema simpleTypeSchema = schema.asSimpleTypeSchema();
+ simpleTypeSchema.setLinks(linkDescriptionObjects);
+ if(pathStart != null && pathStart.length() != 0)
+ simpleTypeSchema.setPathStart(pathStart);
+ }
+ }
+
+ private JsonSchema fetchSchema(Class> targetSchema) {
+ if (provider instanceof DefaultSerializerProvider && targetSchema != void.class) {
+ JavaType targetType = provider.constructType(targetSchema);
+ try {
+ if (visitorContext != null) {
+ String seenSchemaUri = visitorContext.getSeenSchemaUri(targetType);
+ if (seenSchemaUri != null) {
+ return new ReferenceSchema(seenSchemaUri);
+ }
+ }
+ HyperSchemaFactoryWrapper targetVisitor = new HyperSchemaFactoryWrapper();
+ targetVisitor.setVisitorContext(visitorContext);
+
+ ((DefaultSerializerProvider) provider).acceptJsonFormatVisitor(targetType, targetVisitor);
+ return targetVisitor.finalSchema();
+ } catch (JsonMappingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return null;
+ }
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/customProperties/TitleSchemaFactoryWrapper.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/customProperties/TitleSchemaFactoryWrapper.java
new file mode 100644
index 00000000..734df74a
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/customProperties/TitleSchemaFactoryWrapper.java
@@ -0,0 +1,81 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.customProperties;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.JsonSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.ArrayVisitor;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.ObjectVisitor;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.SchemaFactoryWrapper;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.VisitorContext;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.WrapperFactory;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.*;
+
+/**
+ * Adds a title to every object schema, either root level or nested. Generally
+ * useful for writing additional properties to a schema.
+ *
+ * @author jphelan
+ */
+public class TitleSchemaFactoryWrapper extends SchemaFactoryWrapper
+{
+ private static class TitleSchemaFactoryWrapperFactory extends WrapperFactory {
+ @Override
+ public SchemaFactoryWrapper getWrapper(SerializerProvider p) {
+ SchemaFactoryWrapper wrapper = new TitleSchemaFactoryWrapper();
+ if (p != null) {
+ wrapper.setProvider(p);
+ }
+ return wrapper;
+ };
+
+ @Override
+ public SchemaFactoryWrapper getWrapper(SerializerProvider p, VisitorContext rvc) {
+ SchemaFactoryWrapper wrapper = new TitleSchemaFactoryWrapper();
+ if (p != null) {
+ wrapper.setProvider(p);
+ }
+ wrapper.setVisitorContext(rvc);
+ return wrapper;
+ }
+ };
+
+ public TitleSchemaFactoryWrapper() {
+ super(new TitleSchemaFactoryWrapperFactory());
+ }
+
+ @Override
+ public JsonObjectFormatVisitor expectObjectFormat(JavaType convertedType) {
+ ObjectVisitor visitor = ((ObjectVisitor)super.expectObjectFormat(convertedType));
+
+ // could add other properties here
+ addTitle(visitor.getSchema(), convertedType);
+
+ return visitor;
+ }
+
+ @Override
+ public JsonArrayFormatVisitor expectArrayFormat(JavaType convertedType) {
+ ArrayVisitor visitor = ((ArrayVisitor)super.expectArrayFormat(convertedType));
+
+ // could add other properties here
+ addTitle(visitor.getSchema(), convertedType);
+
+ return visitor;
+ }
+
+ /**
+ * Adds writes the type as the title of the schema.
+ *
+ * @param schema The schema who's title to set.
+ * @param type The type of the object represented by the schema.
+ */
+ private void addTitle(JsonSchema schema, JavaType type)
+ {
+ if (!schema.isSimpleTypeSchema()) {
+ throw new RuntimeException("given non simple type schema: " + schema.getType());
+ }
+ schema.asSimpleTypeSchema().setTitle(type.getGenericSignature());
+ }
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/customProperties/ValidationSchemaFactoryWrapper.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/customProperties/ValidationSchemaFactoryWrapper.java
new file mode 100644
index 00000000..7d12a96e
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/customProperties/ValidationSchemaFactoryWrapper.java
@@ -0,0 +1,100 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.customProperties;
+
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.JsonSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.ObjectVisitor;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.ObjectVisitorDecorator;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.SchemaFactoryWrapper;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.VisitorContext;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.WrapperFactory;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ArraySchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.NumberSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ObjectSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.StringSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.*;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.validation.AnnotationConstraintResolver;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.validation.ValidationConstraintResolver;
+
+/**
+ * @author cponomaryov
+ */
+public class ValidationSchemaFactoryWrapper extends SchemaFactoryWrapper {
+
+ private ValidationConstraintResolver constraintResolver;
+
+ private static class ValidationSchemaFactoryWrapperFactory extends WrapperFactory {
+ @Override
+ public SchemaFactoryWrapper getWrapper(SerializerProvider p) {
+ SchemaFactoryWrapper wrapper = new ValidationSchemaFactoryWrapper();
+ wrapper.setProvider(p);
+ return wrapper;
+ }
+
+ @Override
+ public SchemaFactoryWrapper getWrapper(SerializerProvider p, VisitorContext rvc) {
+ SchemaFactoryWrapper wrapper = new ValidationSchemaFactoryWrapper();
+ wrapper.setProvider(p);
+ wrapper.setVisitorContext(rvc);
+ return wrapper;
+ }
+ }
+
+ public ValidationSchemaFactoryWrapper() {
+ this(new AnnotationConstraintResolver());
+ }
+
+ public ValidationSchemaFactoryWrapper(ValidationConstraintResolver constraintResolver) {
+ super(new ValidationSchemaFactoryWrapperFactory());
+ this.constraintResolver = constraintResolver;
+ }
+
+ @Override
+ public JsonObjectFormatVisitor expectObjectFormat(JavaType convertedType) {
+ return new ObjectVisitorDecorator((ObjectVisitor) super.expectObjectFormat(convertedType)) {
+ private JsonSchema getPropertySchema(BeanProperty writer) {
+ return ((ObjectSchema) getSchema()).getProperties().get(writer.getName());
+ }
+
+ @Override
+ public void optionalProperty(BeanProperty writer) throws JsonMappingException {
+ super.optionalProperty(writer);
+ addValidationConstraints(getPropertySchema(writer), writer);
+ }
+
+ @Override
+ public void property(BeanProperty writer) throws JsonMappingException {
+ super.property(writer);
+ addValidationConstraints(getPropertySchema(writer), writer);
+ }
+ };
+ }
+
+ protected JsonSchema addValidationConstraints(JsonSchema schema, BeanProperty prop) {
+ {
+ Boolean required = constraintResolver.getRequired(prop);
+ if (required != null) {
+ schema.setRequired(required);
+ }
+ }
+ if (schema.isArraySchema()) {
+ ArraySchema arraySchema = schema.asArraySchema();
+ arraySchema.setMaxItems(constraintResolver.getArrayMaxItems(prop));
+ arraySchema.setMinItems(constraintResolver.getArrayMinItems(prop));
+ } else if (schema.isNumberSchema()) {
+ NumberSchema numberSchema = schema.asNumberSchema();
+ numberSchema.setMaximum(constraintResolver.getNumberMaximum(prop));
+ numberSchema.setMinimum(constraintResolver.getNumberMinimum(prop));
+ } else if (schema.isStringSchema()) {
+ StringSchema stringSchema = schema.asStringSchema();
+ stringSchema.setMaxLength(constraintResolver.getStringMaxLength(prop));
+ stringSchema.setMinLength(constraintResolver.getStringMinLength(prop));
+ stringSchema.setPattern(constraintResolver.getStringPattern(prop));
+ }
+ return schema;
+ }
+
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/AnyVisitor.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/AnyVisitor.java
new file mode 100644
index 00000000..70a0d7d5
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/AnyVisitor.java
@@ -0,0 +1,31 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.factories;
+
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonAnyFormatVisitor;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.AnySchema;
+
+public class AnyVisitor extends JsonAnyFormatVisitor.Base
+ implements JsonSchemaProducer
+{
+ protected final AnySchema schema;
+
+ public AnyVisitor(AnySchema schema) {
+ this.schema = schema;
+ }
+
+ /*
+ /*********************************************************************
+ /* JsonSchemaProducer
+ /*********************************************************************
+ */
+
+ @Override
+ public AnySchema getSchema() {
+ return schema;
+ }
+
+ /*
+ /*********************************************************************
+ /* AnyVisitor: no additional methods...
+ /*********************************************************************
+ */
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/ArrayVisitor.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/ArrayVisitor.java
new file mode 100644
index 00000000..5345d535
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/ArrayVisitor.java
@@ -0,0 +1,100 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.factories;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.JsonSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ArraySchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ReferenceSchema;
+
+public class ArrayVisitor extends JsonArrayFormatVisitor.Base
+ implements JsonSchemaProducer, Visitor
+{
+ protected final ArraySchema schema;
+
+ protected SerializerProvider provider;
+
+ private WrapperFactory wrapperFactory;
+
+ private VisitorContext visitorContext;
+
+ public ArrayVisitor(SerializerProvider provider, ArraySchema schema) {
+ this(provider, schema, new WrapperFactory());
+ }
+
+ public ArrayVisitor(SerializerProvider provider, ArraySchema schema, WrapperFactory wrapperFactory) {
+ this.provider = provider;
+ this.schema = schema;
+ this.wrapperFactory = wrapperFactory;
+ }
+
+ /*
+ /*********************************************************************
+ /* JsonSchemaProducer
+ /*********************************************************************
+ */
+
+ @Override
+ public JsonSchema getSchema() {
+ return schema;
+ }
+
+ /*
+ /*********************************************************************
+ /* JsonArrayFormatVisitor
+ /*********************************************************************
+ */
+
+ @Override
+ public SerializerProvider getProvider() {
+ return provider;
+ }
+
+ @Override
+ public void setProvider(SerializerProvider p) {
+ provider = p;
+ }
+
+ public WrapperFactory getWrapperFactory() {
+ return wrapperFactory;
+ }
+
+ public void setWrapperFactory(WrapperFactory wrapperFactory) {
+ this.wrapperFactory = wrapperFactory;
+ }
+
+ @Override
+ public void itemsFormat(JsonFormatVisitable handler, JavaType contentType)
+ throws JsonMappingException
+ {
+ // An array of object matches any values, thus we leave the schema empty.
+ if (contentType.getRawClass() != Object.class) {
+
+ // check if we've seen this sub-schema already and return a reference-schema if we have
+ if (visitorContext != null) {
+ String seenSchemaUri = visitorContext.getSeenSchemaUri(contentType);
+ if (seenSchemaUri != null) {
+ schema.setItemsSchema(new ReferenceSchema(seenSchemaUri));
+ return;
+ }
+ }
+
+ SchemaFactoryWrapper visitor = wrapperFactory.getWrapper(getProvider(), visitorContext);
+ handler.acceptJsonFormatVisitor(visitor, contentType);
+ schema.setItemsSchema(visitor.finalSchema());
+ }
+ }
+
+ @Override
+ public void itemsFormat(JsonFormatTypes format) throws JsonMappingException
+ {
+ schema.setItemsSchema(JsonSchema.minimalForFormat(format));
+ }
+
+ @Override
+ public Visitor setVisitorContext(VisitorContext rvc) {
+ visitorContext = rvc;
+ return this;
+ }
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/BooleanVisitor.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/BooleanVisitor.java
new file mode 100644
index 00000000..7c78fc0e
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/BooleanVisitor.java
@@ -0,0 +1,44 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.factories;
+
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.BooleanSchema;
+import java.util.Set;
+
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonBooleanFormatVisitor;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonValueFormat;
+
+public class BooleanVisitor extends JsonBooleanFormatVisitor.Base
+ implements JsonSchemaProducer
+{
+ protected final BooleanSchema schema;
+
+ public BooleanVisitor(BooleanSchema schema) {
+ this.schema = schema;
+ }
+
+ /*
+ /*********************************************************************
+ /* JsonSchemaProducer
+ /*********************************************************************
+ */
+
+ @Override
+ public BooleanSchema getSchema() {
+ return schema;
+ }
+
+ /*
+ /*********************************************************************
+ /* JsonBooleanFormatVisitor impl
+ /*********************************************************************
+ */
+
+ @Override
+ public void enumTypes(Set enums) {
+ schema.setEnums(enums);
+ }
+
+ @Override
+ public void format(JsonValueFormat format) {
+ schema.setFormat(format);
+ }
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/FormatVisitorFactory.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/FormatVisitorFactory.java
new file mode 100644
index 00000000..d81ac3f6
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/FormatVisitorFactory.java
@@ -0,0 +1,105 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.factories;
+
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.*;
+
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.AnySchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ArraySchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.BooleanSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.IntegerSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.NullSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.NumberSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ObjectSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.StringSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.*;
+
+/**
+ * Factory class used for constructing visitors for building various
+ * JSON Schema instances via visitor interface.
+ */
+public class FormatVisitorFactory {
+
+ private final WrapperFactory wrapperFactory;
+
+ public FormatVisitorFactory() {
+ this(new WrapperFactory());
+ }
+
+ public FormatVisitorFactory(WrapperFactory wrapperFactory) {
+ this.wrapperFactory = wrapperFactory;
+ }
+
+ /*
+ /**********************************************************
+ /* Factory methods for visitors, structured types
+ /**********************************************************
+ */
+
+ public JsonAnyFormatVisitor anyFormatVisitor(AnySchema anySchema) {
+ return null;
+ }
+
+ public JsonArrayFormatVisitor arrayFormatVisitor(SerializerProvider provider,
+ ArraySchema arraySchema) {
+ return new ArrayVisitor(provider, arraySchema, wrapperFactory);
+ }
+
+ public JsonMapFormatVisitor mapFormatVisitor(SerializerProvider provider,
+ ObjectSchema objectSchema) {
+ return new MapVisitor(provider, objectSchema, wrapperFactory);
+ }
+
+ public JsonObjectFormatVisitor objectFormatVisitor(SerializerProvider provider,
+ ObjectSchema objectSchema) {
+ return new ObjectVisitor(provider, objectSchema, wrapperFactory);
+ }
+
+
+ protected JsonArrayFormatVisitor arrayFormatVisitor(SerializerProvider provider,
+ ArraySchema arraySchema, VisitorContext rvc) {
+ ArrayVisitor v = new ArrayVisitor(provider, arraySchema, wrapperFactory);
+ v.setVisitorContext(rvc);
+ return v;
+ }
+
+ protected JsonMapFormatVisitor mapFormatVisitor(SerializerProvider provider,
+ ObjectSchema objectSchema, VisitorContext rvc) {
+ MapVisitor v = new MapVisitor(provider, objectSchema, wrapperFactory);
+ v.setVisitorContext(rvc);
+ return v;
+ }
+
+ protected JsonObjectFormatVisitor objectFormatVisitor(SerializerProvider provider,
+ ObjectSchema objectSchema, VisitorContext rvc) {
+ ObjectVisitor v = new ObjectVisitor(provider, objectSchema, wrapperFactory);
+ v.setVisitorContext(rvc);
+ return v;
+ }
+
+ /*
+ /**********************************************************
+ /* Factory methods for visitors, value types
+ /**********************************************************
+ */
+
+ public JsonBooleanFormatVisitor booleanFormatVisitor(BooleanSchema booleanSchema) {
+ return new BooleanVisitor(booleanSchema);
+ }
+
+ public JsonIntegerFormatVisitor integerFormatVisitor(IntegerSchema integerSchema) {
+ return new IntegerVisitor(integerSchema);
+ }
+
+ // no ValueTypeSchemaFactory, since null type has no formatting
+ public JsonNullFormatVisitor nullFormatVisitor(NullSchema nullSchema) {
+ return new NullVisitor(nullSchema);
+ }
+
+ public JsonNumberFormatVisitor numberFormatVisitor(NumberSchema numberSchema) {
+ return new NumberVisitor(numberSchema);
+ }
+
+ public JsonStringFormatVisitor stringFormatVisitor(StringSchema stringSchema) {
+ return new StringVisitor(stringSchema);
+ }
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/IntegerVisitor.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/IntegerVisitor.java
new file mode 100644
index 00000000..c6d90329
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/IntegerVisitor.java
@@ -0,0 +1,44 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.factories;
+
+import java.util.Set;
+
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonIntegerFormatVisitor;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonValueFormat;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.IntegerSchema;
+
+public class IntegerVisitor extends JsonIntegerFormatVisitor.Base
+ implements JsonSchemaProducer
+{
+ protected final IntegerSchema schema;
+
+ public IntegerVisitor(IntegerSchema schema) {
+ this.schema = schema;
+ }
+
+ /*
+ /*********************************************************************
+ /* JsonSchemaProducer
+ /*********************************************************************
+ */
+
+ @Override
+ public IntegerSchema getSchema() {
+ return schema;
+ }
+
+ /*
+ /*********************************************************************
+ /* JsonIntegerFormatVisitor
+ /*********************************************************************
+ */
+
+ @Override
+ public void enumTypes(Set enums) {
+ schema.setEnums(enums);
+ }
+
+ @Override
+ public void format(JsonValueFormat format) {
+ schema.setFormat(format);
+ }
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/JsonSchemaFactory.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/JsonSchemaFactory.java
new file mode 100644
index 00000000..38aeb25c
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/JsonSchemaFactory.java
@@ -0,0 +1,45 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.factories;
+
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.AnySchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ArraySchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.BooleanSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.IntegerSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.NullSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.NumberSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ObjectSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.StringSchema;
+
+public class JsonSchemaFactory
+{
+ public AnySchema anySchema() {
+ return new AnySchema();
+ }
+
+ public ArraySchema arraySchema() {
+ return new ArraySchema();
+ }
+
+ public BooleanSchema booleanSchema() {
+ return new BooleanSchema();
+ }
+
+ public IntegerSchema integerSchema() {
+ return new IntegerSchema();
+ }
+
+ public NullSchema nullSchema() {
+ return new NullSchema();
+ }
+
+ public NumberSchema numberSchema() {
+ return new NumberSchema();
+ }
+
+ public ObjectSchema objectSchema() {
+ return new ObjectSchema();
+ }
+
+ public StringSchema stringSchema() {
+ return new StringSchema();
+ }
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/JsonSchemaProducer.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/JsonSchemaProducer.java
new file mode 100644
index 00000000..6ef5e7ab
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/JsonSchemaProducer.java
@@ -0,0 +1,13 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.factories;
+
+import com.fasterxml.jackson.module.jsonSchema.jakarta.JsonSchema;
+
+/**
+ * Interface for objects that produce {@link JsonSchema} instances;
+ * implemented by visitors.
+ *
+ * @author jphelan
+ */
+public interface JsonSchemaProducer {
+ public JsonSchema getSchema();
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/MapVisitor.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/MapVisitor.java
new file mode 100644
index 00000000..af4f7281
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/MapVisitor.java
@@ -0,0 +1,103 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.factories;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonMapFormatVisitor;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.JsonSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ObjectSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ReferenceSchema;
+
+/**
+ * While JSON Schema does not have notion of "Map" type (unlimited property
+ * names), Jackson has, so the distinction is exposed. We will need
+ * to handle it here, produce JSON Schema Object type.
+ */
+public class MapVisitor extends JsonMapFormatVisitor.Base
+ implements JsonSchemaProducer, Visitor
+{
+ protected final ObjectSchema schema;
+
+ protected SerializerProvider provider;
+
+ private WrapperFactory wrapperFactory;
+
+ private VisitorContext visitorContext;
+
+ public MapVisitor(SerializerProvider provider, ObjectSchema schema) {
+ this(provider, schema, new WrapperFactory());
+ }
+
+ public MapVisitor(SerializerProvider provider, ObjectSchema schema, WrapperFactory wrapperFactory) {
+ this.provider = provider;
+ this.schema = schema;
+ this.wrapperFactory = wrapperFactory;
+ }
+
+ /*
+ /*********************************************************************
+ /* JsonSchemaProducer
+ /*********************************************************************
+ */
+
+ @Override
+ public ObjectSchema getSchema() {
+ return schema;
+ }
+
+ /*
+ /*********************************************************************
+ /* JsonMapFormatVisitor
+ /*********************************************************************
+ */
+
+ @Override
+ public SerializerProvider getProvider() {
+ return provider;
+ }
+
+ @Override
+ public void setProvider(SerializerProvider p) {
+ provider = p;
+ }
+
+ @Override
+ public void keyFormat(JsonFormatVisitable handler, JavaType keyType)
+ throws JsonMappingException {
+ // JSON Schema only allows String types so let's not bother too much
+ }
+
+ @Override
+ public void valueFormat(JsonFormatVisitable handler, JavaType valueType)
+ throws JsonMappingException {
+
+ // ISSUE #24: https://github.com/FasterXML/jackson-module-jsonSchema/issues/24
+
+ JsonSchema valueSchema = propertySchema(handler, valueType);
+ ObjectSchema.AdditionalProperties ap = new ObjectSchema.SchemaAdditionalProperties(valueSchema.asSimpleTypeSchema());
+ this.schema.setAdditionalProperties(ap);
+ }
+
+ protected JsonSchema propertySchema(JsonFormatVisitable handler, JavaType propertyTypeHint)
+ throws JsonMappingException {
+
+ // check if we've seen this sub-schema already and return a reference-schema if we have
+ if (visitorContext != null) {
+ String seenSchemaUri = visitorContext.getSeenSchemaUri(propertyTypeHint);
+ if (seenSchemaUri != null) {
+ return new ReferenceSchema(seenSchemaUri);
+ }
+ }
+
+ SchemaFactoryWrapper visitor = wrapperFactory.getWrapper(getProvider(), visitorContext);
+ handler.acceptJsonFormatVisitor(visitor, propertyTypeHint);
+ return visitor.finalSchema();
+ }
+
+ @Override
+ public Visitor setVisitorContext(VisitorContext rvc) {
+ visitorContext = rvc;
+ return this;
+ }
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/NullVisitor.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/NullVisitor.java
new file mode 100644
index 00000000..e1d337f5
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/NullVisitor.java
@@ -0,0 +1,19 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.factories;
+
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonNullFormatVisitor;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.NullSchema;
+
+public class NullVisitor extends JsonNullFormatVisitor.Base
+ implements JsonSchemaProducer
+{
+ protected final NullSchema schema;
+
+ public NullVisitor(NullSchema schema) {
+ this.schema = schema;
+ }
+
+ @Override
+ public NullSchema getSchema() {
+ return schema;
+ }
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/NumberVisitor.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/NumberVisitor.java
new file mode 100644
index 00000000..1c1e81f0
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/NumberVisitor.java
@@ -0,0 +1,44 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.factories;
+
+import java.util.Set;
+
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonNumberFormatVisitor;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonValueFormat;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.NumberSchema;
+
+public class NumberVisitor extends JsonNumberFormatVisitor.Base
+ implements JsonSchemaProducer
+{
+ protected final NumberSchema schema;
+
+ public NumberVisitor(NumberSchema schema) {
+ this.schema = schema;
+ }
+
+ /*
+ /*********************************************************************
+ /* JsonSchemaProducer
+ /*********************************************************************
+ */
+
+ @Override
+ public NumberSchema getSchema() {
+ return schema;
+ }
+
+ /*
+ /*********************************************************************
+ /* JsonNumberFormatVisitor
+ /*********************************************************************
+ */
+
+ @Override
+ public void enumTypes(Set enums) {
+ schema.setEnums(enums);
+ }
+
+ @Override
+ public void format(JsonValueFormat format) {
+ schema.setFormat(format);
+ }
+}
diff --git a/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/ObjectVisitor.java b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/ObjectVisitor.java
new file mode 100644
index 00000000..851075f9
--- /dev/null
+++ b/jakarta/src/main/java/com/fasterxml/jackson/module/jsonSchema/jakarta/factories/ObjectVisitor.java
@@ -0,0 +1,158 @@
+package com.fasterxml.jackson.module.jsonSchema.jakarta.factories;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
+import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.JsonSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ObjectSchema;
+import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ReferenceSchema;
+
+public class ObjectVisitor extends JsonObjectFormatVisitor.Base
+ implements JsonSchemaProducer, Visitor
+{
+ protected final ObjectSchema schema;
+ protected SerializerProvider provider;
+ private WrapperFactory wrapperFactory;
+ private VisitorContext visitorContext;
+
+ /**
+ * @deprecated Since 2.4; call constructor that takes {@link WrapperFactory}
+ */
+ @Deprecated
+ public ObjectVisitor(SerializerProvider provider, ObjectSchema schema) {
+ this(provider, schema, new WrapperFactory());
+ }
+
+ public ObjectVisitor(SerializerProvider provider, ObjectSchema schema, WrapperFactory wrapperFactory) {
+ this.provider = provider;
+ this.schema = schema;
+ this.wrapperFactory = wrapperFactory;
+ }
+
+ /*
+ /*********************************************************************
+ /* JsonSchemaProducer
+ /*********************************************************************
+ */
+
+ @Override
+ public ObjectSchema getSchema() {
+ return schema;
+ }
+
+ /*
+ /*********************************************************************
+ /* JsonObjectFormatVisitor impl
+ /*********************************************************************
+ */
+
+ @Override
+ public SerializerProvider getProvider() {
+ return provider;
+ }
+
+ /**
+ * @deprecated Construct instances with provider instead
+ */
+ @Deprecated
+ @Override
+ public void setProvider(SerializerProvider p) {
+ provider = p;
+ }
+
+ public WrapperFactory getWrapperFactory() {
+ return wrapperFactory;
+ }
+
+ /**
+ * @deprecated Construct instances with provider instead
+ */
+ @Deprecated
+ public void setWrapperFactory(WrapperFactory wrapperFactory) {
+ this.wrapperFactory = wrapperFactory;
+ }
+
+ @Override
+ public void optionalProperty(BeanProperty prop) throws JsonMappingException {
+ schema.putOptionalProperty(prop, propertySchema(prop));
+ }
+
+ @Override
+ public void optionalProperty(String name, JsonFormatVisitable handler, JavaType propertyTypeHint)
+ throws JsonMappingException {
+ schema.putOptionalProperty(name, propertySchema(handler, propertyTypeHint));
+ }
+
+ @Override
+ public void property(BeanProperty prop) throws JsonMappingException {
+ schema.putProperty(prop, propertySchema(prop));
+ }
+
+ @Override
+ public void property(String name, JsonFormatVisitable handler, JavaType propertyTypeHint)
+ throws JsonMappingException {
+ schema.putProperty(name, propertySchema(handler, propertyTypeHint));
+ }
+
+ protected JsonSchema propertySchema(BeanProperty prop)
+ throws JsonMappingException
+ {
+ if (prop == null) {
+ throw new IllegalArgumentException("Null property");
+ }
+
+ // check if we've seen this argument's sub-schema already and return a reference-schema if we have
+ String seenSchemaUri = visitorContext.getSeenSchemaUri(prop.getType());
+ if (seenSchemaUri != null) {
+ return new ReferenceSchema(seenSchemaUri);
+ }
+
+ SchemaFactoryWrapper visitor = wrapperFactory.getWrapper(getProvider(), visitorContext);
+ JsonSerializer