Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * Copyright (c) 2012, Francis Galiegue <fgaliegue@gmail.com>
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the Lesser GNU General Public License as
   * published by the Free Software Foundation, either version 3 of the
   * License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * Lesser GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 package com.github.fge.jsonschema.examples;
 
 
 import java.util.List;
 import java.util.Set;
 
 import static com.github.fge.jsonschema.messages.SyntaxMessages.*;

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.jsonschema.util.NodeType,com.github.fge.jsonschema.util.NodeType[]) and com.github.fge.jsonschema.library.KeywordBuilder.withSimpleDigester(com.github.fge.jsonschema.util.NodeType,com.github.fge.jsonschema.util.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");
        final Keyword keyword = Keyword.newBuilder("divisors")
            .withSyntaxChecker(DivisorsSyntaxChecker.getInstance())
            .withDigester(DivisorsDigesters.getInstance())
            .withValidatorClass(DivisorsKeywordValidator.class).freeze();
        final Library library = DraftV4Library.get().thaw()
            .addKeyword(keyword).freeze();
        final ValidationConfiguration cfg = ValidationConfiguration.newBuilder()
            .setDefaultLibrary("http://my.site/myschema#"library).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 ProcessingReport reportfinal 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(tree));
                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(tree)
                        .put("expected".)
                        .put("found"type));
                else if (element.bigIntegerValue().compareTo(.) < 0)
                    report.error(newMsg(tree)
                        .put("value"element));
                uniqueItems = set.add(element);
            }
            if (!uniqueItems)
                report.error(newMsg(tree));
        }
    }
    /*
     * 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 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(data)
                .message("integer value is not a multiple of all divisors")
                .put("divisors").put("failed"failed));
        }
        @Override
        public String toString()
        {
            return "divisors: " + ;
        }
    }
New to GrepCode? Check out our FAQ X