当前位置:网站首页>Networknt:: JSON schema validator source code appreciation

Networknt:: JSON schema validator source code appreciation

2022-06-23 14:11:00 rhyechen

Json Is a self explanatory language , Widely used in request protocols 、 The configuration file 、 Format specification and other scenarios . For constraint Json data format , Need to use another special Json data -- JsonSchema standard .

Official website https://json-schema.org/ Recommended. snow、vert.x、everit-org、networknt Several kinds Java Realization , among networknt It has been widely used with excellent performance , Today, let's analyze networknt Of Java Version implementation .


Code warehouse : https://github.com/networknt/json-schema-validator edition (1.0.64)

Core summary

Various prefabricated validator Inherit from BaseJsonValidator, There are two important ways walk(...) and validate(...) . Both functions and return values are similar ,walk Method support in validate Method to call the registered PreWalkListeners/PostWalkListeners Section method , You can implement some custom functions in it , For example, printing logs .

Various prefabricated validator Need to be in ValidatorTypeCode Register in , Let's take a look ValidatorTypeCode Part of the declared core code :

public enum ValidatorTypeCode implements Keyword, ErrorMessageType {
    ...
    MAXIMUM("maximum", "1011", new MessageFormat(I18nSupport.getString("maximum")), MaximumValidator.class, 15),
    ...
    IF_THEN_ELSE("if", "1037", null, IfValidator.class, 12),
    ...
    private ValidatorTypeCode(String value, String errorCode, MessageFormat messageFormat, Class validator, long versionCode) {
        this.value = value;
        this.errorCode = errorCode;
        this.messageFormat = messageFormat;
        this.errorCodeKey = value + "ErrorCode";
        this.validator = validator;
        this.versionCode = versionCode;
        this.customMessage = null;
    }
    
    ...
    public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) throws Exception {
        if (validator == null) {
            throw new UnsupportedOperationException("No suitable validator for " + getValue());
        }
        // if the config version is not match the validator
        @SuppressWarnings("unchecked")
        Constructor<JsonValidator> c = ((Class<JsonValidator>) validator).getConstructor(
                new Class[]{String.class, JsonNode.class, JsonSchema.class, ValidationContext.class});
        return c.newInstance(schemaPath + "/" + getValue(), schemaNode, parentSchema, validationContext);
    }
    
    ...    
}

Have you found any problems ?

Although each check element is validator, But after registering to Factory Is actually being ValidatorTypeCode ( A kind of Keyword Realization ) Packed one by one . When it is necessary to expand into validator when , By registering class Type to find the constructor with fixed signature and instantiate .

Don't worry about performance ,validators Be being JsonSchema Lazy load and hold , It will only be initialized once and will accompany the whole JsonSchema The entire life cycle of the instance will not change .

That is how to determine what needs to be loaded validators Well ? Here we need to mention Json-Schema The Syntax version of ( see https://json-schema.org/specification-links.html).networknt At present, we support v4、v6、v7、v2019-09 edition , Each version will specify specific check keywords .

ValidatorTypeCode There is one of them. versionCode attribute , Every validator At the time of registration, you have indicated your version . For easy storage versionCode The applicable version of... Is marked by a binary mask . for example MAXIMUM:15 = 8 + 4 + 2 + 1 ( Applicable to all versions ),IF_THEN_ELSE:12 = 8 + 4 ( apply v2019-09 and v7 ).

versionCode Mask

The registration information for each Syntax version is in JsonMetaSchema in , With v6 For example :

     private static class V6 {
        private static String URI = "https://json-schema.org/draft-06/schema";
        // Draft 6 uses "$id"
        private static final String ID = "$id";

        public static final List<Format> BUILTIN_FORMATS = new ArrayList<Format>(JsonMetaSchema.COMMON_BUILTIN_FORMATS);

        static {
            // add version specific formats here.
            //BUILTIN_FORMATS.add(pattern("phone", "^\\+(?:[0-9] ?){6,14}[0-9]$"));
        }

        public static JsonMetaSchema getInstance() {
            return new Builder(URI)
                    .idKeyword(ID)
                    .addFormats(BUILTIN_FORMATS)
                    .addKeywords(ValidatorTypeCode.getNonFormatKeywords(SpecVersion.VersionFlag.V6))
                    // keywords that may validly exist, but have no validation aspect to them
                    .addKeywords(Arrays.asList(
                            new NonValidationKeyword("$schema"),
                            new NonValidationKeyword("$id"),
                            new NonValidationKeyword("title"),
                            new NonValidationKeyword("description"),
                            new NonValidationKeyword("default"),
                            new NonValidationKeyword("definitions")
                    ))
                    .build();
        }
    }        

It mainly consists of two parts :ValidatorTypeCode( The version corresponds to validators),NonValidationKeyword( System keyword corresponding to version ). Both types implement self Keyword keyword , Users can also use Keyword Implement custom dialect .

At the beginning, I talked about Json-Schema It's a special kind Json data , therefore validators The whole process of building is to json-schema tree Analytic process . Key code :

public JsonSchema extends BaseJsonValidator {
    ...
    /**
     * Please note that the key in {@link #validators} map is a schema path. It is
     * used in {@link com.networknt.schema.walk.DefaultKeywordWalkListenerRunner} to derive the keyword.
     */
    private Map<String, JsonValidator> read(JsonNode schemaNode) {
        Map<String, JsonValidator> validators = new TreeMap<>(VALIDATOR_SORT);
        if (schemaNode.isBoolean()) {
            if (schemaNode.booleanValue()) {
                final String customMessage = getCustomMessage(schemaNode, "true");
                JsonValidator validator = validationContext.newValidator(getSchemaPath(), "true", schemaNode, this, customMessage);
                validators.put(getSchemaPath() + "/true", validator);
            } else {
                final String customMessage = getCustomMessage(schemaNode, "false");
                JsonValidator validator = validationContext.newValidator(getSchemaPath(), "false", schemaNode, this, customMessage);
                validators.put(getSchemaPath() + "/false", validator);
            }
        } else {
            Iterator<String> pnames = schemaNode.fieldNames();
            while (pnames.hasNext()) {
                String pname = pnames.next();
                JsonNode nodeToUse = pname.equals("if") ? schemaNode : schemaNode.get(pname);
                String customMessage = getCustomMessage(schemaNode, pname);
                JsonValidator validator = validationContext.newValidator(getSchemaPath(), pname, nodeToUse, this, customMessage);
                if (validator != null) {
                    validators.put(getSchemaPath() + "/" + pname, validator);

                    if (pname.equals("required")) {
                        requiredValidator = validator;
                    }
                }

            }
        }
        return validators;
    }
    ...
}

It seems only on the first floor schema Did validators Generation , Actually properties And so on , Each of them has its own substructure validators ( See PropertiesValidator).

The above is the definition of various syntax keywords and validators Registration process , Here's an atom checker MaxItemsValidator For example , Analyze the inspection process in detail .

// schema  Configuration style : "maxItems": 5

public class MaxItemsValidator extends BaseJsonValidator implements JsonValidator {

    private static final Logger logger = LoggerFactory.getLogger(MaxItemsValidator.class);

    private final ValidationContext validationContext;

    private int max = 0;

    public MaxItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_ITEMS, validationContext);
        if (schemaNode.canConvertToExactIntegral()) {
            max = schemaNode.intValue();
        }
        this.validationContext = validationContext;
        parseErrorCode(getValidatorType().getErrorCodeKey());
    }

    public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
        debug(logger, node, rootNode, at);

        if (node.isArray()) {
            if (node.size() > max) {
                return Collections.singleton(buildValidationMessage(at, "" + max));
            }
        } else if (this.validationContext.getConfig().isTypeLoose()) {
            if (1 > max) {
                return Collections.singleton(buildValidationMessage(at, "" + max));
            }
        }

        return Collections.emptySet();
    }

}

Generate validator Instance when calling the constructor schemaPath and schemaNode , Get the maximum number of configured elements and save them in max Properties of the .

When traversing the data to the corresponding node Node time , Will check the corresponding validators, find maxitems And call validate Method , This method first judges the current node Is it array type ,true Then continue to judge whether the array length exceeds the maximum limit , Otherwise, the wrong type will be thrown .

When a hit error occurs, the public buildValidationMessage (String at, String... arguments) Method .at Used to indicate that the current error occurred in json tree Specific hierarchical location of , arguments Is used to fill in ValidatorTypeCode Declarative MessageFormat Parameter placeholder for .

maxItems = {0}: there must be a maximum of {1} items in the array

After filling, it is : { route } : The number of elements under cannot exceed the maximum {max} individual .

It should be noted that the error is in the form of a set Set Form return of ,ValidationMessage Of equals Methods have also been specially treated :

public class ValidationMessage {
    ...
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ValidationMessage that = (ValidationMessage) o;

        if (type != null ? !type.equals(that.type) : that.type != null) return false;
        if (code != null ? !code.equals(that.code) : that.code != null) return false;
        if (path != null ? !path.equals(that.path) : that.path != null) return false;
        if (details != null ? !details.equals(that.details) : that.details != null) return false;
        if (!Arrays.equals(arguments, that.arguments)) return false;
        return !(message != null ? !message.equals(that.message) : that.message != null);

    }
    ...
}

notes : from 1.0.58 The version began to add multiple languages ( in / english ) Error reporting support for , And added the function of customizing errors , Let's not go into details here .


That's right networknt Of json-schema-validator Analysis of core source code .

To sum up, there are two main points :

1、 Atomization of the checker , In the later stage, it can be nested deeply through configuration and combination .

2、 Tree traversal ,Schema The initialization phase recursively generates validators ,Data Recursive triggers validators.

There are many things worth learning about design, such as the nesting of atomic components 、 Faceting listener Buried point .

But there is also a lot of hard coding in the code , With reflection keyword To validator Mapping is not particularly elegant .

As the version iterates , Gradually added i18n、customMessage And more new features , I will continue to follow up on some new features , Welcome to exchange and study together .

原网站

版权声明
本文为[rhyechen]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/01/202201072054064011.html