Ejemplo Java de meta-esquema v1

Un ejemplo de Java para validar cargas útiles con instancias del metaesquema de definición de tipo de producto de Amazon.

Ejemplo de implementación del validador para Java

Para aplicaciones Java, la biblioteca networknt/json-schema-validator es compatible con JSON Schema Draft 2019-09 y vocabularios personalizados. El siguiente ejemplo demuestra cómo utilizar la biblioteca networknt/json-schema-validator para validar cargas útiles con instancias del metaesquema de definición de tipo de producto de Amazon . No hay ningún requisito para usar esta biblioteca específica o la implementación de ejemplo. Amazon no proporciona soporte técnico para bibliotecas de esquemas JSON de terceros y esto se proporciona solo como ejemplo.

Configuración del Schema

Cuando se utiliza networknt/json-schema-validator para validar instancias del metaesquema de definición de tipo de producto de Amazon con vocabulario personalizado, el metaesquema se configura como parte del JsonSchemaFactory .

constantes :

// $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");

Configurar 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))));

Validación de Payload

Con una instancia del metaesquema de definición de tipo de producto de Amazon cargado como JsonSchema instance, payloads can be validated using the instance.

// Create a JsonNode for the payload (this can be constructed in code, read from a file, etc.).
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);

Validación de Keyword

El networknt/json-schema-validator admite la validación de vocabulario personalizado mediante el uso de clases que amplían el clase AbstractKeyword y proporcione la lógica de validación.

Consulte https://github.com/networknt/json-schema-validator/blob/master/doc/validators.md .

Los siguientes ejemplos ilustran extensiones de la Clase AbstractKeyword que valida el vocabulario personalizado en instancias del metaesquema de definición de tipo de producto de Amazon .

MaxUniqueItemsKeyword Class

import 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

package 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

package 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();
            }
        };
    }
}