Edit

Share via


How to customize parsing and validation in System.CommandLine

Important

System.CommandLine is currently in PREVIEW, and this documentation is for version 2.0 beta 5. Some information relates to prerelease product that may be substantially modified before it's released. Microsoft makes no warranties, express or implied, with respect to the information provided here.

By default, System.CommandLine provides a set of built-in parsers that can parse many common types:

  • bool
  • byte and sbyte
  • short and ushort
  • int and uint
  • long and ulong
  • float and double
  • decimal
  • DateTime and DateTimeOffset
  • DateOnlyand TimeOnly
  • Guid
  • FileSystemInfo, FileInfo, and DirectoryInfo
  • enums
  • arrays and lists of the listed types

Other types are not supported, but you can create custom parsers for them. You can also validate the parsed values, which is useful when you want to ensure that the input meets certain criteria.

Validators

Every option, argument, and command can have one or more validators. Validators are used to ensure that the parsed value meets certain criteria. For example, you can validate that a number is positive, or that a string is not empty. You can also create complex validators that check against multiple conditions.

Every symbol type in System.CommandLine has a Validators property that contains a list of validators. The validators are executed after the input is parsed, and they can report an error if the validation fails.

To provide custom validation code, call System.CommandLine.Option.Validators.Add on your option or argument (or command), as shown in the following example:

Option<int> delayOption = new("--delay");
delayOption.Validators.Add(result =>
{
    if (result.GetValue(delayOption) < 1)
    {
        result.AddError("Must be greater than 0");
    }
});

System.CommandLine provides a set of built-in validators that can be used to validate common types:

  • AcceptExistingOnly - configures given option or argument to accept only values corresponding to an existing file or directory.
  • AcceptLegalFileNamesOnly - configures given option or argument to accept only values representing legal file names.
  • AcceptOnlyFromAmong - configures given option or argument to accept only values from a specified set of values.

Custom parsers

Custom parsers are required to parse types with no default parser, such as complex types. They can also be used to parse supported types in a different way than the built-in parsers.

Suppose you have a Person type:

public class Person
{
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
}

You can read the values and create an instance of Person in the command action:

rootCommand.SetAction(parseResult =>
{
    Person person = new()
    {
        FirstName = parseResult.GetValue(firstNameOption),
        LastName = parseResult.GetValue(lastNameOption)
    };
    DoRootCommand(parseResult.GetValue(fileOption), person);
});

With a custom parser, you can get a custom type the same way you get primitive values:

Option<Person?> personOption = new("--person")
{
    Description = "An option whose argument is parsed as a Person",
    CustomParser = result =>
    {
        if (result.Tokens.Count != 2)
        {
            result.AddError("--person requires two arguments");
            return null;
        }
        return new Person
        {
            FirstName = result.Tokens.First().Value,
            LastName = result.Tokens.Last().Value
        };
    }
};

If you want to parse as well as validate the input, use the CustomParser delegate, as shown in the following example:

Option<int> delayOption = new("--delay")
{
    Description = "An option whose argument is parsed as an int.",
    CustomParser = result =>
    {
        if (!result.Tokens.Any())
        {
            return 42;
        }

        if (int.TryParse(result.Tokens.Single().Value, out var delay))
        {
            if (delay < 1)
            {
                result.AddError("Must be greater than 0");
            }
            return delay;
        }
        else
        {
            result.AddError("Not an int.");
            return 0; // Ignored.
        }
    }
};

Here are some examples of what you can do with CustomParser that you can't do with a validator:

  • Parse other kinds of input strings (for example, parse "1,2,3" into int[]).
  • Dynamic arity. For example, if you have two arguments that are defined as string arrays, and you have to handle a sequence of strings in the command-line input, the System.CommandLine.Parsing.ArgumentResult.OnlyTake method enables you to dynamically divide up the input strings between the arguments.

See also