Exemplo em C# de Meta-Schema (v1)

Um exemplo em C# para validar payloads com instâncias do Meta-Schema de definição de tipo de produto da Amazon.

Exemplo de implementação do validador para .NET

Para aplicativos C# .NET, a biblioteca Newtonsoft Json.NET Schema é compatível com JSON Schema Draft 2019-09 e vocabulários personalizados. O exemplo a seguir demonstra como utilizar a biblioteca Newtonsoft Json.NET Schema para validar cargas com instâncias do Meta-Schema de definição de tipo de produto da Amazon. Não há necessidade de usar essa biblioteca específica ou a implementação de exemplo. A Amazon não fornece suporte técnico para bibliotecas de esquema JSON de terceiros, isso é fornecido apenas como exemplo.

Configuração do Schema

Ao usar o Newtonsoft Json.NET Schema para validar instâncias do Meta-Schema de definição de tipo de produto da Amazon com vocabulário personalizado, o Meta-Schema é configurado com um JSchemaResolver e a validação de palavra-chave personalizada é configurada com JSchemaReaderSettings ao analisar instâncias do Meta-Schema de definição de tipo de produto da Amazon.

Constantes:

// $id of the Amazon Product Type Definition Meta-Schema.
var schemaId = "https://schemas.amazon.com/selling-partners/definitions/product-types/meta-schema/v1";

// Local copy of the Amazon Product Type Definition Meta-Schema.
var metaSchemaPath = "./amazon-product-type-definition-meta-schema-v1.json";

// Local copy of an instance of the Amazon Product Type Definition Meta-Schema.
var luggageSchemaPath = "./luggage.json";

Configurar Meta-Schema:

// Schema resolver that uses local copies of schemas rather than retrieving them from the web.
var resolver = new JSchemaPreloadedResolver();

// Add the meta-schema to the resolver.
resolver.Add(new Uri(schemaId), File.ReadAllText(metaSchemaPath));

// Configure reader settings with resolver and keyword validators to use when parsing instances of the meta-schema.
var readerSettings = new JSchemaReaderSettings
    {
        Resolver = resolver,
        Validators = new List<JsonValidator>
        {
            new MaxUniqueItemsKeywordValidator(),
            new MaxUtf8ByteLengthKeywordValidator(),
            new MinUtf8ByteLengthKeywordValidator()
        }
    };

Carregar instância do Meta-Schema:

var luggageSchema = JSchema.Parse(File.ReadAllText(luggageSchemaPath), readerSettings);

Validação do Payload

Com uma instância do Meta-Schema de definição de tipo de produto da Amazon carregada como uma instância JSchema, as cargas úteis podem ser validadas usando a instância.

// Create a JObject for the payload (this can be constructed in code, read from a file, etc.).
var payload = JObject.Parse(File.ReadAllText("./payload.json"));

// Validate the payload and get any resulting validation messages.
var valid = payload.IsValid(luggageSchema, out IList<string> errorMessages);

Se a carga útil for válida, IsValid retornará true com uma lista vazia de mensagens de erro. Caso contrário, IsValid retornará false com uma lista de mensagens de erro.

Validação de palavra-chave

O Newtonsoft Json.NET Schema dá suporte à validação de vocabulário personalizado usando classes que implementam a interface JsonValidator e fornecem a lógica de validação.

Consulte https://www.newtonsoft.com/jsonschema/help/html/CustomJsonValidators.htm.

Os exemplos a seguir ilustram as implementações da interface JsonValidator que validam o vocabulário personalizado em instâncias do Meta-Schema de definição de tipo de produto da Amazon.

Classe MaxUniqueItemsKeyword

using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;

namespace AmazonProductTypeSchemaValidator
{
    /**
     * Example validator for the "maxUniqueItems" keyword.
     */
    public class MaxUniqueItemsKeywordValidator : JsonValidator
    {
        public override void Validate(JToken value, JsonValidatorContext context)
        {
            var maxUniqueItems = GetMaxUniqueItems(context.Schema);
            
            // Get the selector properties configured on the scheme element, if they exist. Otherwise, this validator
            // defaults to using all properties.
            var selectors = GetSelectors(context.Schema);

            // Only process if the value is an array with values.
            if (value.Type != JTokenType.Array) return;
            
            // Create a property-value dictionary of each items properties (selectors) and count the number of
            // occurrences for each combination.
            var uniqueItemCounts = new Dictionary<IDictionary<string, string>, int>(new UniqueKeyComparer());
            foreach (var instance in value)
            {
                // Only process instances in the array that are objects.
                if (instance.Type != JTokenType.Object) continue;

                var instanceObject = JObject.FromObject(instance);
                var uniqueKeys = instanceObject.Properties()
                    .Where(property => selectors.Count == 0 || selectors.Contains(property.Name))
                    .ToDictionary(property => property.Name, property => property.Value.ToString());

                var count = uniqueItemCounts.GetValueOrDefault(uniqueKeys, 0) + 1;
                uniqueItemCounts[uniqueKeys] = count;
            }
            
            // Find first selector combination with too many instances.
            var (uniqueKey, itemCount) = uniqueItemCounts.FirstOrDefault(entry => entry.Value > maxUniqueItems);
            if (itemCount > 0)
            {
                var selectorValues = string.Join(", ", uniqueKey.Select(keyValuePair => $"{keyValuePair.Key}={keyValuePair.Value}").ToList());
                context.RaiseError($"Each combination of selector values may only occur {maxUniqueItems} times. " +
                                   $"The following selector value combination occurs too many times: {{{selectorValues}}}");
            }
        }

        public override bool CanValidate(JSchema schema)
        {
            return GetMaxUniqueItems(schema) >= 0;
        }

        private static IList<string> GetSelectors(JSchema schema)
        {
            var selectors = new List<string>();
            
            var schemaObject = JObject.FromObject(schema);
            var selectorsProperty = schemaObject["selectors"];

            if (selectorsProperty.HasValues)
                selectors.AddRange(selectorsProperty.Select(selector => selector.ToString()));

            return selectors;
        }

        private static int GetMaxUniqueItems(JSchema schema)
        {
            var schemaObject = JObject.FromObject(schema);
            var maxUniqueItemsProperty = schemaObject["maxUniqueItems"];

            if (maxUniqueItemsProperty != null && int.TryParse(maxUniqueItemsProperty.ToString(), out var maxUniqueItems))
                return maxUniqueItems;

            return -1;
        }

        /**
         * "Deep" comparator for unique keys dictionary to enable use as a dictionary key.
         */
        private class UniqueKeyComparer : IEqualityComparer<IDictionary<string, string>>
        {
            public bool Equals(IDictionary<string, string> x, IDictionary<string, string> y)
            {
                return x.Count == y.Count 
                       && x.Aggregate(true, (current, keyValuePair) => current && keyValuePair.Value == y[keyValuePair.Key]);
            }

            public int GetHashCode(IDictionary<string, string> obj)
            {
                return ("Keys_" + string.Join(",", obj.Select(o => o.Key))
                        + "_Values_" + string.Join(",", obj.Select(o => o.Value))).GetHashCode();
            }
        }
    }
}

Classe MaxUtf8ByteLengthKeyword

using System.Text;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;

namespace AmazonProductTypeSchemaValidator
{
    /**
     * Example validator for the "maxUtf8ByteLength" keyword.
     */
    public class MaxUtf8ByteLengthKeywordValidator : JsonValidator
    {
        public override void Validate(JToken value, JsonValidatorContext context)
        {
            var maxUtf8ByteLength = GetMaxUtf8ByteLength(context.Schema);
            if (Encoding.UTF8.GetBytes(value.ToString()).Length > maxUtf8ByteLength)
                context.RaiseError($"Value must be less than or equal {maxUtf8ByteLength} bytes in length.");
        }

        public override bool CanValidate(JSchema schema)
        {
            return GetMaxUtf8ByteLength(schema) >= 0;
        }

        private static int GetMaxUtf8ByteLength(JSchema schema)
        {
            var schemaObject = JObject.FromObject(schema);
            var byteLengthProperty = schemaObject["maxUtf8ByteLength"];

            if (byteLengthProperty != null && int.TryParse(byteLengthProperty.ToString(), out var maxUtf8ByteLength))
                return maxUtf8ByteLength;

            return -1;
        }
    }
}

Classe MinUtf8ByteLengthKeyword

using System.Text;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;

namespace AmazonProductTypeSchemaValidator
{
    /**
     * Example validator for the "minUtf8ByteLength" keyword.
     */
    public class MinUtf8ByteLengthKeywordValidator : JsonValidator
    {
        public override void Validate(JToken value, JsonValidatorContext context)
        {
            var minUtf8ByteLength = GetMinUtf8ByteLength(context.Schema);
            if (Encoding.UTF8.GetBytes(value.ToString()).Length < minUtf8ByteLength)
                context.RaiseError($"Value must be greater than or equal {minUtf8ByteLength} bytes in length.");
        }

        public override bool CanValidate(JSchema schema)
        {
            return GetMinUtf8ByteLength(schema) >= 0;
        }

        private static int GetMinUtf8ByteLength(JSchema schema)
        {
            var schemaObject = JObject.FromObject(schema);
            var byteLengthProperty = schemaObject["minUtf8ByteLength"];

            if (byteLengthProperty != null && int.TryParse(byteLengthProperty.ToString(), out var minUtf8ByteLength))
                return minUtf8ByteLength;

            return -1;
        }
    }
}