Skip to content

Add validator and additionalProperties bool to Unity#80

Open
thorminate wants to merge 7 commits intoVedalAI:mainfrom
thorminate:feat/QJS-validator
Open

Add validator and additionalProperties bool to Unity#80
thorminate wants to merge 7 commits intoVedalAI:mainfrom
thorminate:feat/QJS-validator

Conversation

@thorminate
Copy link

@thorminate thorminate commented Jan 28, 2026

Adds a built-in validator to QJS for ActionJData, JToken, and plain objects.
Full support for schema constraints like type, const, enum, required properties, min/max, pattern, arrays and the new additionalProperties bool.

additionalProperties just dictates if any other properties beyond the schema are allowed. It could be used to prevent typos or unintended fields by Neuro

Fully backward-compatible.

It has been briefly tested on my own personal project and seems to working fine, please let me know if any changes are needed.

Note: this does not replace existing action validation logic, it just ensures all the properties you expect exist and is of the desired type.


A decently large validator for a pretty large schema could look like this

if (actionData == null)
{
    parsedData = null;
    return ExecutionResult.Failure("Action failed. Data must be an object.");
}

var obj = (JObject)actionData?.Data;
parsedData = new Dictionary<string, object>();

var targets = (string?)obj["target"];
if (targets is null)
{
    parsedData = null;
    return ExecutionResult.Failure("Action failed. 'target' is required and must be a string.");
}
if (!targets.StartsWith("E") && !targets.StartsWith("I") && !targets.StartsWith("P"))
{
    parsedData = null;
    return ExecutionResult.Failure("Action failed. 'target' IDs must be prefixed with E, I, or P.");
}
parsedData["target"] = target;

if (obj["parameters"] is JObject parameters)
{
    var paramDict = new Dictionary<string, object>();

    var speed = parameters["speed"];
    if (speed != null)
    {
        if (speed.Type != JTokenType.Float && speed.Type != JTokenType.Integer)
            return ExecutionResult.Failure("Action failed. 'parameters.speed' must be a number.");

        paramDict["speed"] = speed.Value<double>();
    }

    var direction = parameters["direction"] as JObject;
    if (direction != null)
    {
        var dirDict = new Dictionary<string, double>();

        var x = direction["x"];
        if (x != null)
        {
            if (x.Type != JTokenType.Float && x.Type != JTokenType.Integer)
                return ExecutionResult.Failure("Action failed. 'parameters.direction.x' must be a number.");
            dirDict["x"] = x.Value<double>();
        }

        var y = direction["y"];
        if (y != null)
        {
            if (y.Type != JTokenType.Float && y.Type != JTokenType.Integer)
                return ExecutionResult.Failure("Action failed. 'parameters.direction.y' must be a number.");
            dirDict["y"] = y.Value<double>();
        }

        if (dirDict.Count > 0)
            paramDict["direction"] = dirDict;
    }

    if (paramDict.Count > 0)
        parsedData["parameters"] = paramDict;
}

var flags = obj["flags"] as JObject;
if (flags != null)
{
    var flagsDict = new Dictionary<string, object>();

    var silent = flags["silent"];
    if (silent != null)
    {
        if (silent.Type != JTokenType.Boolean)
            return ExecutionResult.Failure("Action failed. 'flags.silent' must be a boolean.");

        flagsDict["silent"] = silent.Value<bool>();
    }

    if (flagsDict.Count > 0)
        parsedData["flags"] = flagsDict;
}

but with the validator, all that could be compressed to this: (given that the schema is fed adequate data, like regex patterns or enums)

if (!Schema.ValidateSafe(actionData, out var msg))
{
    parsedData = null;
    return ExecutionResult.Failure("Action failed. " + msg!);
}

parsedData = new()
{
    ["target"] = actionData.GetValue<string>("target")!
};

if (actionData.GetValue<JObject>("parameters") is { } p)
{
    var pd = new Dictionary<string, object>();
    if (p.GetValue<double?>("speed") is { } s) pd["speed"] = s;
    if (p.GetValue<JObject>("direction") is { } d)
    {
        var dd = new Dictionary<string, double>();
        if (d.GetValue<double?>("x") is { } x) dd["x"] = x;
        if (d.GetValue<double?>("y") is { } y) dd["y"] = y;
        if (dd.Count != 0) pd["direction"] = dd;
    }
    if (pd.Count != 0) parsedData["parameters"] = pd;
}

if (actionData.GetValue<JObject>("flags") is { } f &&
    f.GetValue<bool?>("silent") is { } silent)
{
    parsedData["flags"] = new Dictionary<string, object> { ["silent"] = silent };
}

All and all, with these changes along with my other pull requests, this should help encourage making proper schemas and decrease bugs in validation code.

@thorminate
Copy link
Author

i forgor to remove a debug logger lol

@thorminate
Copy link
Author

thorminate commented Jan 30, 2026

This should work fine now 🤞

@thorminate
Copy link
Author

thorminate commented Feb 4, 2026

@Alexejhero is it better to keep the additionalProperties bool ignored by json? as the spec does specifically say that that schema prop doesn't really do much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants