Skip to content

axonivy/expressive-schema-module

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

expressive-schema-module

Maven Central build

An extension module for victools jsonschema-generator. We supply frequently required json-schema hints by introducing annotations, that can be directly placed onto your domain models. This with the idea in mind, to keep you programming as much java as possible, without having to dive into the jsonschema generator specifics.

Annotations

@Examples

Simply annotate any field with @Examples, in order to supply valid values for it:

@Examples({"superFastProvider", "speedBuster", "cheapAndSlow"})
public String provider;

Will generate a schema as follows:

"properties" : {
  "provider" : {
    "type" : "string",
    "examples" : [ "superFastProvider", "speedBuster", "cheapAndSlow" ]
  }
}

@RemoteRef

Simplifies the inclusion of other schemas, therefore enabling you to craft composite schemas.

@RemoteRef("/ivy/a-sibling.json")
public Object sibling;

Will generate a schema as follows:

"sibling" : {
  "$ref" : "/ivy/a-sibling.json"
}

@Condition

Enables you to inject sibling schemas based on a selected constant.

@Condition(ifConst  = "azure-idp", thenProperty = "config", thenRef = "/ivy/azure-config.json")
public String provider;

Will generate a schema as follows:

"properties" : {
  "provider" : {
    "type" : "string"
  }
},
"if" : {
  "properties" : {
    "provider" : {
      "const" : "azure-idp"
    }
  }
},
"then" : {
  "properties" : {
    "config" : {
      "$ref" : "/ivy/azure-config.json"
    }
  }
}

Note that multiple conditions can be combined by using many instances of @Condition on a single field:

@Condition(ifConst  = "azure-idp", thenProperty = "config", thenRef = "/ivy/azure-config.json")
@Condition(ifConst  = "ms-ad", thenProperty = "config", thenRef = "/ivy/ldap-config.json")
public String provider;

@Implementations

Adds implementations of a generic type into the schema. It will use a virtual type and value properties in order to provide strict schema support, despite the generic class design.

@Implementations(ComponentTypes)
public Component component;

public static class ComponentTypes implements TypeReqistry {
  @Override
  public Set<Class<?>> types() {
    return Set.of(Specific.class, Another.class);
  }
}

Will generate a schema as follows:

"Component" : {
  "type" : "object",
  "properties" : {
    "type" : {
      "type" : "string",
      "enum" : [ "Another", "Specific" ]
    },
    "config" : {
      "type" : "object"
    }
  },
  "additionalProperties" : false,
  "allOf" : [ {
    "if" : {
      "properties" : {
        "type" : {
          "const" : "Specific"
        }
      }
    },
    "then" : {
      "properties" : {
        "config" : {
          "$ref" : "#/$defs/Specific"
        }
      }
    }
  } ]
}

@TypesAsFields

Adds implementations of a generic type to explicit field references. This is perfect if you maintain a Map with well known keys in your Java objects.

public CheckTypes checks;

@TypesAsFields(Checks)
public interface CheckTypes implements Map<String, MyChecker>{}

public static class Checks implements FieldRegistry {
  @Override
  public Set<Class<?>> types() {
    return Set.of(Specific.class, Another.class);
  }
}

Will generate a schema as follows:

"CheckTypes" : {
  "type" : "object",
  "additionalProperties" : {
    "$ref" : "#/$defs/MyChecker"
  },
  "properties" : {
    "Another" : {
      "$ref" : "#/$defs/Another"
    },
    "Specific" : {
      "$ref" : "#/$defs/Specific"
    }
  }
}

@AdditionalProperties

With the ExpressiveSchemaOption.USE_ADDITIONAL_PROPERTIES_ANNOTATION the schema only allows properties, that are actually outlined in your object models. By adding the @AdditionalProperties annotation however, you can allow unspecified properties again.

static class AnyFieldsSchema {
  public Product product;

  @AdditionalProperties
  public static class Product {
    public String id;
  }
}

Produces therefore:

"$schema" : "https://json-schema.org/draft/2019-09/schema",
"$defs" : {
  "Product" : {
    "type" : "object",
    "properties" : {
      "id" : {
        "type" : "string"
      }
    }
  }
},
"type" : "object",
"properties" : {
  "product" : {
    "$ref" : "#/$defs/Product"
  }
},
"additionalProperties" : false

@CustomType

Allows to patch a type definition, to represent it in another way within the schema.

@CustomType(TestCustomType.Provider.class)
public Object provider;

Generates:

"properties" : {
  "provider" : {
    "$ref" : "#/$defs/Provider"
  }
}

Options

The module comes with a few opt-in schema features. See the ExpressiveSchemaOption enumeration.

  • USE_ADDITIONAL_PROPERTIES_ANNOTATION: disables any properties, unless you actually allow them by using the @AdditionalProperties annotation.
  • USE_JACKSON_JSON_PROPERTY_DEFAULT_VALUE: contributes default values to the jsonschema by using Jacksons @JsonPropert(defaultValue="myDefault") annotation for the task.
  • USE_SCHEMA_SUFFIX_NAMING_STRATEGY: provides a naming strategy that cleans your types from 'Schema' suffixes. We use this for object models that were invented just for the sake of documenting jsonschema features. So we added 'Schema' postfixes to make them distinguable from real productive objects. In the generated schema however, these long names are removed.

Examples

Enable all options programatically:

var configBuilder = new SchemaGeneratorConfigBuilder(VERSION, OptionPreset.PLAIN_JSON);
configBuilder.with(Option.DEFINITIONS_FOR_ALL_OBJECTS);
configBuilder.with(new ExpressiveSchemaModule(EnumSet.allOf(ExpressiveSchemaOption.class)));