Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com)
   *
   * This software is dual-licensed under:
   *
   * - the Lesser General Public License (LGPL) version 3.0 or, at your option, any
   *   later version;
   * - the Apache Software License (ASL) version 2.0.
   *
  * The text of this file and of both licenses is available at the root of this
  * project or, if you have the jar distribution, in directory META-INF/, under
  * the names LGPL-3.0.txt and ASL-2.0.txt respectively.
  *
  * Direct link to the sources:
  *
  * - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt
  * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt
  */
 
 package com.github.fge.jsonschema.examples;
 
 
 import java.util.List;
 import java.util.Set;
Ninth example: augmenting schemas with custom keywords

link to source code

link to schema

This example adds a custom keyword with syntax checking, digesting and keyword validation. The chosen keyword is divisors: it applies to integer values and takes an array of (unique) integers as an argument.

The validation is the same as for multipleOf except that it is restricted to integer values and the instance must be a multiple of all divisors. For instance, if the value of this keyword is [2, 3], then 6 validates successfully but 14 does not (it is divisible by 2 but not 3).

For this, you need to create your own keyword. This is done using com.github.fge.jsonschema.library.Keyword.newBuilder(java.lang.String), where the argument is the name of your keyword, and then add the following elements:

Then, as in Example8, you need to get hold of a com.github.fge.jsonschema.library.Library (we choose again to extend the draft v4 library) and add the (frozen) keyword to it using com.github.fge.jsonschema.library.LibraryBuilder.addKeyword(com.github.fge.jsonschema.library.Keyword).

The keyword validator must have a single constructor taking a com.fasterxml.jackson.databind.JsonNode as an argument (which will be the result of the com.github.fge.jsonschema.keyword.digest.Digester). Note that you may omit to write a digester and choose instead to use an com.github.fge.jsonschema.keyword.digest.helpers.IdentityDigester or a com.github.fge.jsonschema.keyword.digest.helpers.SimpleDigester (which you inject into a keyword using com.github.fge.jsonschema.library.KeywordBuilder.withIdentityDigester(com.github.fge.jackson.NodeType,com.github.fge.jackson.NodeType[]) and com.github.fge.jsonschema.library.KeywordBuilder.withSimpleDigester(com.github.fge.jackson.NodeType,com.github.fge.jackson.NodeType[]) respectively).

Two sample files are given: the first (link) is valid, the other (link) isn't (the first and third elements fail to divide by one or more factors).

public final class Example9
    public static void main(final String... args)
        throws IOExceptionProcessingException
    {
        final JsonNode customSchema = Utils.loadResource("/custom-keyword.json");
        final JsonNode good = Utils.loadResource("/custom-keyword-good.json");
        final JsonNode bad = Utils.loadResource("/custom-keyword-bad.json");
        /*
         * Build the new keyword
         */
        final Keyword keyword = Keyword.newBuilder("divisors")
            .withSyntaxChecker(DivisorsSyntaxChecker.getInstance())
            .withDigester(DivisorsDigesters.getInstance())
            .withValidatorClass(DivisorsKeywordValidator.class).freeze();
        /*
         * Build a library, based on the v4 library, with this new keyword
         */
        final Library library = DraftV4Library.get().thaw()
            .addKeyword(keyword).freeze();
        /*
         * Complement the validation message bundle with a dedicated message
         * for our keyword validator
         */
        final String key = "missingDivisors";
        final String value = "integer value is not a multiple of all divisors";
        final MessageSource source = MapMessageSource.newBuilder()
            .put(keyvalue).build();
        final MessageBundle bundle
            = MessageBundles.getBundle(JsonSchemaValidationBundle.class)
            .thaw().appendSource(source).freeze();
        /*
         * Build a custom validation configuration: add our custom library and
         * message bundle
         */
        final ValidationConfiguration cfg = ValidationConfiguration.newBuilder()
            .setDefaultLibrary("http://my.site/myschema#"library)
            .setValidationMessages(bundle).freeze();
        final JsonSchemaFactory factory = JsonSchemaFactory.newBuilder()
            .setValidationConfiguration(cfg).freeze();
        final JsonSchema schema = factory.getJsonSchema(customSchema);
        ProcessingReport report;
        report = schema.validate(good);
        ..println(report);
        report = schema.validate(bad);
        ..println(report);
    }
    /*
     * Our custom syntax checker
     */
    private static final class DivisorsSyntaxChecker
        extends AbstractSyntaxChecker
    {
        private static final SyntaxChecker INSTANCE
            = new DivisorsSyntaxChecker();
        public static SyntaxChecker getInstance()
        {
            return ;
        }
        private DivisorsSyntaxChecker()
        {
            /*
             * When constructing, the name for the keyword must be provided
             * along with the allowed type for the value (here, an array).
             */
            super("divisors".);
        }
        @Override
        protected void checkValue(final Collection<JsonPointerpointers,
            final MessageBundle bundlefinal ProcessingReport report,
            final SchemaTree tree)
            throws ProcessingException
        {
            /*
             * Using AbstractSyntaxChecker as a base, we know that when we reach
             * this method, the value has already been validated as being of
             * the allowed primitive types (only array here).
             *
             * But this is not enough for this particular validator: we must
             * also ensure that all elements of this array are integers. Cycle
             * through all elements of the array and check each element. If we
             * encounter a non integer argument, add a message.
             *
             * We must also check that there is at lease one element, that the
             * array contains no duplicates and that all elements are positive
             * integers and strictly greater than 0.
             *
             * The getNode() method grabs the value of this keyword for us, so
             * use that. Note that we also reuse some messages already defined
             * in SyntaxMessages.
             */
            final JsonNode node = getNode(tree);
            final int size = node.size();
            if (size == 0) {
                report.error(newMsg(treebundle"emptyArray"));
                return;
            }
            NodeType type;
            JsonNode element;
            boolean uniqueItems = true;
            final Set<JsonNodeset = Sets.newHashSet();
            for (int index = 0; index < sizeindex++) {
                element = node.get(index);
                type = NodeType.getNodeType(element);
                if (type != .)
                    report.error(newMsg(treebundle"incorrectElementType")
                        .put("expected".)
                        .put("found"type));
                else if (element.bigIntegerValue().compareTo(.) < 0)
                    report.error(newMsg(treebundle"integerIsNegative")
                        .put("value"element));
                uniqueItems = set.add(element);
            }
            if (!uniqueItems)
                report.error(newMsg(treebundle"elementsNotUnique"));
        }
    }
    /*
     * Our custom digester
     *
     * We take the opportunity to build a digested form where, for instance,
     * [ 3, 5 ] and [ 5, 3 ] give the same digest.
     */
    private static final class DivisorsDigesters
        extends AbstractDigester
    {
        private static final Digester INSTANCE = new DivisorsDigesters();
        public static Digester getInstance()
        {
            return ;
        }
        private DivisorsDigesters()
        {
            super("divisors".);
        }
        @Override
        public JsonNode digest(final JsonNode schema)
        {
            final SortedSet<JsonNodeset = Sets.newTreeSet();
            for (final JsonNode elementschema.get())
                set.add(element);
            return .arrayNode().addAll(set);
        }
        /*
         * Custom Comparator. We compare BigInteger values, since all integers
         * are representable using this class.
         */
        private static final Comparator<JsonNodeCOMPARATOR
            = new Comparator<JsonNode>()
        {
            @Override
            public int compare(final JsonNode o1final JsonNode o2)
            {
                return o1.bigIntegerValue().compareTo(o2.bigIntegerValue());
            }
        };
    }


    
Custom keyword validator for Example9 It must be public because it is built by reflection.
    public static final class DivisorsKeywordValidator
        extends AbstractKeywordValidator
    {
        /*
         * We want to validate arbitrarily large integer values, we therefore
         * use BigInteger.
         */
        private final List<BigIntegerdivisors;
        public DivisorsKeywordValidator(final JsonNode digest)
        {
            super("divisors");
            final ImmutableList.Builder<BigIntegerlist
                = ImmutableList.builder();
            for (final JsonNode elementdigest)
                list.add(element.bigIntegerValue());
             = list.build();
        }
        @Override
        public void validate(final Processor<FullDataFullDataprocessor,
            final ProcessingReport reportfinal MessageBundle bundle,
            final FullData data)
            throws ProcessingException
        {
            final BigInteger value
                = data.getInstance().getNode().bigIntegerValue();
            /*
             * We use a plain list to store failed divisors: remember that the
             * digested form was built with divisors in order, we therefore
             * only need insertion order, and a plain ArrayList guarantees that.
             */
            final List<BigIntegerfailed = Lists.newArrayList();
            for (final BigInteger divisor)
                if (!value.mod(divisor).equals(.))
                    failed.add(divisor);
            if (failed.isEmpty())
                return;
            /*
             * There are missed divisors: report.
             */
            report.error(newMsg(databundle"missingDivisors")
                .put("divisors").put("failed"failed));
        }
        @Override
        public String toString()
        {
            return "divisors: " + ;
        }
    }
New to GrepCode? Check out our FAQ X