yury
yury

Reputation: 1

Pass context to JsonValidator

I'm implementing custom schema validation rules using JsonValidator class. My validations need additional information, which is specific for every JSON instance being validated.

In Json.NET Schema custom validators are attached to JSchema object, which is recommended to be reused for better performance. Therefore instance-specific information cannot be associated with the JsonValidator object, which will be used for multiple JSON instances.

Is it possible to pass a context object for each JToken.IsValid() call?

I tried checking whether using JSchemaValidatingReader directly can help, but I didn't find a way to access the reader from JsonValidator.Validate(). Other option I checked was associating the context with the JToken object containing the entire JSON instance and accessing it through the Root property of the JToken value parameter passed to Validate(), but it turns out that the Root property refers to something else.

And another question, which is actually a prerequisite for talking about JSchema reuse. Is JSchema object thread-safe and re-entrant? Can it be used for multiple concurrent validations of different JSON instances?


Adding a code example to demonstrate the problem.

Suppose I have a simple name-value schema for a "setting". The value needs to match a certain type, corresponding to the name. This is not expressed in the schema itself and may vary, depending on what "settings group" the setting belongs to.

This is a registry of possible setting groups. In reality it is dynamic and unlimited number of such groups can be registered.

internal static class SettingsRegistry
{
    public static readonly Dictionary<string, string> SettingsGroup1 =
        new()
        {
            { "A", "string" },
            { "B", "integer" },
        };

    public static readonly Dictionary<string, string> SettingsGroup2 =
        new()
        {
            { "A", "boolean" },
            { "B", "string" },
        };
}

This is a custom validator which should, based on the "name" property, find the expected type and check that the "value" matches that type. The problem is that it doesn't know which settings group to use.

internal class ValueTypeValidator : JsonValidator
{
    // For simplicity of the example
    public override bool CanValidate(JSchema schema) => true;

    public override void Validate(JToken value, JsonValidatorContext context)
    {
        if (value.Type == JTokenType.Object)
        {
            var obj = (JObject)value;

            var name = (string)obj["name"];
            var type = GetExpectedValueType(name);
            var schema = JSchema.Parse($@"{{ 'type': '{type}' }}");

            var valueToken = obj["value"];
            if (!valueToken.IsValid(schema))
            {
                context.RaiseError($"Type mismatch, expected {type}");
            }
        }
    }

    private string GetExpectedValueType(string name)
    {
        // Here I need to select the correct settings group
        // based on the context of the JSON instance being validated.
        return SettingsRegistry.SettingsGroup2[name];
    }
}

And this is how this custom validation is used.

internal class Program
{
    static void Main(string[] args)
    {
        var schemaJson = @"{
            'description': 'A configuration setting value',
            'type': 'object',
            'properties': {
            'name': {'type': 'string'},
            'value': true
            }
        }";

        var settingJson = @"{ 'name': 'A', 'value': 'XYZ' }";
        var setting = JToken.Parse(settingJson);

        var readerSettings = new JSchemaReaderSettings
        {
            Validators = [new ValueTypeValidator()]
        };

        var schema = JSchema.Parse(schemaJson, readerSettings);

        // Here I'd like to pass some context to IsValid() call which will specify
        // which settings group from the registry this setting should be validated against.
        // ValueTypeValidator.Validate() would use this information in its validation.
        // I can't pass that to ValueTypeValidator's constructor because I want
        // to be able to reuse the same JSchema object for validating multiple settings,
        // each belonging potentially to a different settings group.
        var isValid = setting.IsValid(schema, out IList<string> errors);
        Console.WriteLine(
            "IsValid={0}, Errors:\r\n{1}",
            isValid,
            errors != null ? string.Join("\r\n", errors) : string.Empty);
    }
}

Upvotes: 0

Views: 59

Answers (0)

Related Questions