Java Example of Meta-Schema v1
Validate payloads with Amazon Product Type Definitions meta-schema instances.
Example Validator Implementation for Java
For Java applications, the networknt/json-schema-validator library supports JSON Schema Draft 2019-09 and custom vocabularies. The following example demonstrates how to utilize the networknt/json-schema-validator library to validate payloads with instances of the Amazon Product Type Definition Meta-Schema. There is no requirement to use this specific library or the example implementation. Amazon does not provide technical support for third-party JSON Schema libraries and this is provided as an example only.
Schema Configuration
When using the networknt/json-schema-validator to validate instances of the Amazon Product Type Definition Meta Schema with custom vocabulary, the meta-schema is configured as part of the JsonSchemaFactory
.
Constants:
// $id of the Amazon Product Type Definition Meta-Schema.
String schemaId = "https://schemas.amazon.com/selling-partners/definitions/product-types/meta-schema/v1";
// Local copy of the Amazon Product Type Definition Meta-Schema.
String metaSchemaPath = "./amazon-product-type-definition-meta-schema-v1.json";
// Local copy of an instance of the Amazon Product Type Definition Meta-Schema.
String luggageSchemaPath = "./luggage.json";
// Keywords that are informational only and do not require validation.
List<String> nonValidatingKeywords = ImmutableList.of("editable", "enumNames");
Configure Meta-Schema:
// Standard JSON Schema 2019-09 that Amazon Product Type Definition Meta-Schema extends from.
JsonMetaSchema standardMetaSchema = JsonMetaSchema.getV201909();
// Build Amazon Product Type Definition Meta Schema with the standard JSON Schema 2019-09 as the blueprint.
// Register custom keyword validation classes (see below).
JsonMetaSchema metaSchema = JsonMetaSchema.builder(SCHEMA_ID, standardMetaSchema)
.addKeywords(NON_VALIDATING_KEYWORDS.stream().map(NonValidationKeyword::new)
.collect(Collectors.toSet()))
.addKeyword(new MaxUniqueItemsKeyword())
.addKeyword(new MaxUtf8ByteLengthKeyword())
.addKeyword(new MinUtf8ByteLengthKeyword())
.build();
Build JsonSchemaFactory:
// URIFetcher to route meta-schema references to local copy.
URIFetcher uriFetcher = uri -> {
// Use the local copy of the meta-schema instead of retrieving from the web.
if (schemaId.equalsIgnoreCase(uri.toString())) {
return Files.newInputStream(Paths.get(metaSchemaPath));
}
// Default to the existing fetcher for other schemas.
return new URLFetcher().fetch(uri);
};
// Build the JsonSchemaFactory.
JsonSchemaFactory schemaFactory = new JsonSchemaFactory.Builder()
.defaultMetaSchemaURI(schemaId)
.addMetaSchema(standardMetaSchema)
.addMetaSchema(metaSchema)
.uriFetcher(uriFetcher, "https")
.build();
// Create the JsonSchema instance.
JsonSchema luggageSchema = schemaFactory.getSchema(new String(Files.readAllBytes(Paths.get(luggageSchemaPath))));
Payload Validation
With an instance of the Amazon Product Type Definition Meta-Schema loaded as a JsonSchema
instance, payloads can be validated using the instance.
// Create a JsonNode for the payload (this can be constructed in code or read from a file).
JsonNode payload = new ObjectMapper().readValue(new File("./payload.json"), JsonNode.class);
// Validate the payload and get any resulting validation messages.
Set<ValidationMessage> messages = luggageSchema.validate(payload);
If no validation messages are returned, validation passed. Otherwise, inspect the validation messages to identify errors with the payload.
Keyword Validation
The networknt/json-schema-validator supports validating custom vocabulary by using classes that extend the AbstractKeyword
class and provide the validation logic.
The following examples illustrate extensions of the AbstractKeyword
class that validate the custom vocabulary in instances of the Amazon Product Type Definition Meta-Schema.
MaxUniqueItemsKeyword
Class
MaxUniqueItemsKeyword
Classimport com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.networknt.schema.AbstractJsonValidator;
import com.networknt.schema.AbstractKeyword;
import com.networknt.schema.CustomErrorMessageType;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonValidator;
import com.networknt.schema.ValidationContext;
import com.networknt.schema.ValidationMessage;
import org.apache.commons.lang3.StringUtils;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Example validator for the "maxUniqueItems" keyword.
*/
public class MaxUniqueItemsKeyword extends AbstractKeyword {
private static final MessageFormat ERROR_MESSAGE_FORMAT = new MessageFormat("Each combination of selector "
+ "values may only occur {1} times. The following selector value combination occurs too many times: {2}");
private static final String KEYWORD = "maxUniqueItems";
private static final String SELECTORS = "selectors";
public MaxUniqueItemsKeyword() {
super(KEYWORD);
}
@Override
public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
ValidationContext validationContext) {
// Only process if the provided schema value is a number.
if (!JsonNodeType.NUMBER.equals(schemaNode.getNodeType())) {
return null;
}
int maxUniqueItems = schemaNode.asInt();
// Get the selector properties configured on the scheme element, if they exist. Otherwise, this validator
// defaults to using all properties.
Set<String> selectors = getSelectorProperties(parentSchema);
return new AbstractJsonValidator(this.getValue()) {
@Override
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
// Only process if the node is an array, as selectors and unique items do not apply to other data
// types.
if (node.isArray()) {
// Create a property-value map of each items properties (selectors) and count the number of
// occurrences for each combination.
Map<Map<String, String>, Integer> uniqueItemCounts = Maps.newHashMap();
node.forEach(instance -> {
// Only process instances that are objects.
if (instance.isObject()) {
Map<String, String> uniqueKeys = Maps.newHashMap();
Iterator<Map.Entry<String, JsonNode>> fieldIterator = instance.fields();
while (fieldIterator.hasNext()) {
Map.Entry<String, JsonNode> entry = fieldIterator.next();
// If no selectors are configured, always add. Otherwise only add if the property is
// a selector.
if (selectors.isEmpty() || selectors.contains(entry.getKey())) {
uniqueKeys.put(entry.getKey(), entry.getValue().asText());
}
}
// Iterate count and put in counts map.
int count = uniqueItemCounts.getOrDefault(uniqueKeys, 0) + 1;
uniqueItemCounts.put(uniqueKeys, count);
}
});
// Find first selector combination with too many instances.
Optional<Map<String, String>> uniqueKeysWithTooManyItems = uniqueItemCounts.entrySet()
.stream().filter(entry -> entry.getValue() > maxUniqueItems).map(Map.Entry::getKey)
.findFirst();
// Return a failed validation if a selector combination has too many instances.
if (uniqueKeysWithTooManyItems.isPresent()) {
return fail(CustomErrorMessageType.of(KEYWORD, ERROR_MESSAGE_FORMAT), at,
Integer.toString(maxUniqueItems), uniqueKeysWithTooManyItems.get().toString());
}
}
return pass();
}
};
}
private Set<String> getSelectorProperties(JsonSchema parentSchema) {
if (parentSchema.getSchemaNode().has(SELECTORS) && parentSchema.getSchemaNode().get(SELECTORS).isArray()) {
return Streams.stream(parentSchema.getSchemaNode().get(SELECTORS)).map(JsonNode::asText)
.filter(StringUtils::isNotBlank).collect(Collectors.toSet());
}
return Sets.newHashSet();
}
}
MaxUtf8ByteLengthKeyword
Class
MaxUtf8ByteLengthKeyword
Classpackage com.amazon.spucs.tests.keywords;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.networknt.schema.AbstractJsonValidator;
import com.networknt.schema.AbstractKeyword;
import com.networknt.schema.CustomErrorMessageType;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonValidator;
import com.networknt.schema.ValidationContext;
import com.networknt.schema.ValidationMessage;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.Set;
/**
* Example validator for the "maxUtf8ByteLength" keyword.
*/
public class MaxUtf8ByteLengthKeyword extends AbstractKeyword {
private static final MessageFormat ERROR_MESSAGE_FORMAT =
new MessageFormat("Value must be less than or equal {1} bytes in length.");
private static final String KEYWORD = "maxUtf8ByteLength";
public MaxUtf8ByteLengthKeyword() {
super(KEYWORD);
}
@Override
public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
ValidationContext validationContext) {
// Only process if the provided schema value is a number.
if (!JsonNodeType.NUMBER.equals(schemaNode.getNodeType())) {
return null;
}
int maxUtf8ByteLength = schemaNode.asInt();
return new AbstractJsonValidator(this.getValue()) {
@Override
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
// Get the value as a string and evaluate its length in bytes.
String value = node.asText();
if (value.getBytes(StandardCharsets.UTF_8).length > maxUtf8ByteLength) {
return fail(CustomErrorMessageType.of(KEYWORD, ERROR_MESSAGE_FORMAT), at,
Integer.toString(maxUtf8ByteLength));
}
return pass();
}
};
}
}
MinUtf8ByteLengthKeyword
Class
MinUtf8ByteLengthKeyword
Classpackage com.amazon.spucs.tests.keywords;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.networknt.schema.AbstractJsonValidator;
import com.networknt.schema.AbstractKeyword;
import com.networknt.schema.CustomErrorMessageType;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonValidator;
import com.networknt.schema.ValidationContext;
import com.networknt.schema.ValidationMessage;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.Set;
/**
* Example validator for the "minUtf8ByteLength" keyword.
*/
public class MinUtf8ByteLengthKeyword extends AbstractKeyword {
private static final MessageFormat ERROR_MESSAGE_FORMAT =
new MessageFormat("Value must be greater than or equal {1} bytes in length.");
private static final String KEYWORD = "minUtf8ByteLength";
public MinUtf8ByteLengthKeyword() {
super(KEYWORD);
}
@Override
public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
ValidationContext validationContext) {
// Only process if the provided schema value is a number.
if (!JsonNodeType.NUMBER.equals(schemaNode.getNodeType())) {
return null;
}
int minUtf8ByteLength = schemaNode.asInt();
return new AbstractJsonValidator(this.getValue()) {
@Override
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
// Get the value as a string and evaluate its length in bytes.
String value = node.asText();
if (value.getBytes(StandardCharsets.UTF_8).length < minUtf8ByteLength) {
return fail(CustomErrorMessageType.of(KEYWORD, ERROR_MESSAGE_FORMAT), at,
Integer.toString(minUtf8ByteLength));
}
return pass();
}
};
}
}
Updated 13 days ago