Nightly Pocket Measurement

    adv-parser
    TypeScript icon, indicating that this package has built-in type declarations

    2.0.4 • Public • Published

    adv-parser

    Build Status

    Parser for special simplified syntax of json schema.

    Default json schema

    schema = {
        type: "object",
        additionalProperties: false,
        required: ['id', /* "name", */ 'enabled', 'list', 'user', 'enumOfStrings'],
        properties: {
            id: {
                type: "number",
            },
            name: {
                type: "string",
            },
            enabled: {
                type: "boolean",
            },
            list: {
                type: "array",
                items: {type: "number"}
            },
            user: {
                type: "object",
                additionalProperties: false,
                required: ['id', 'type'],
                properties: {
                    id: {type: 'number'},
                    type: {type: 'string'},
                }
            },
            enumOfStrings: {
                type: "string",
                enum: ["user", "guest", "owner"]
            },
        }
    }

    Simplified syntax of same schema

    schema = {
        id: number,
        [name]: string,
        enabled: boolean,
        list: [number],
        user: {
            id: number,
            type: string,
        },
        enumOfStrings: "user" || "guest" || "owner",
    }

    Usage

    const parser = require('adv-parser');
    
    let schema = parser(`{id: number}`);
    
    // or as arrow function (which will be converted to string and parsed) 
    // if you want free syntax highlighting
    
    schema = parser(() => ({id: number}));
    
    schema == {
        type: 'object',
        additionalProperties: false,
        required: ['id'],
        properties: {
            id: {type: 'number'}
        }
    };

    Schemas cache

    const parser = require('adv-parser');
    const defaultSchemas = require('adv-parser/schemas');
    
    const schemas = {
        ...defaultSchemas
    };
    
    const schema1 = parser(`User = {id: number}`, {schemas});
    const schema2 = parser(`Test.SubTest = {name: string}`, {schemas});
    const schema3 = parser(Product => ({id: uuid}), {schemas});
    const schema4 = parser(() => Company = {name: /^\w+$/}, {schemas});
    
    schema1 == schemas.User;
    schema2 == schemas['Test.SubTest'];
    schema3 == schemas.Product;
    schema4 == schemas.Company;

    Custom methods

    All methods work with the schema as AST. It gives you ability to create your own meta programming language

    More about default methods see Schema methods and Schema options as methods

    const parser = require('adv-parser');
    const defaultMethods = require('adv-parser/methods');
    const {set} = defaultMethods;
    
    const schema = parser(`number.test(true)`, {
        methods: {
            ...defaultMethods,
            
            test: function (schema, args, params) {
                return set(schema, ['test', args[0]], params);
            }
        }
    });
    
    schema == {
        type: 'number',
        test: true,
    };

    Custom functions

    You can define custom functions like

    const parser = require('adv-parser');
    const t = require('@babel/types');
    
    const schema = parser(`{id: test(1, 2)}`, {
        functions: {
            test: function (args) {
                return t.numericLiteral(
                    args.reduce((sum, item) => sum + item.value, 0)
                );
            }
        }
    });
    
    schema == {
        type: 'object',
        test: true,
        additionalProperties: false,
        required: ['id'],
        properties: {
            id: 3
        }
    };

    Custom object inline options

    More about default object inline options see Object schema inline options

    const parser = require('adv-parser');
    const defaultObjectOptions = require('adv-parser/methods/object');
    const set = require('adv-parser/methods/set');
    
    const schema = parser(`{id: number, $test: true}`, {
        objectOptions: {
            ...defaultObjectOptions,
            
            test: function (schema, args, params) {
                return set(schema, ['test', args[0]], params);
            }
        }
    });
    
    schema == {
        type: 'object',
        test: true,
        additionalProperties: false,
        required: ['id'],
        properties: {
            id: {type: 'number'}
        }
    };

    Optional object fields

    By default, all fields in an object are required. To make field optional just put it in brackets.

    schema = {
        id: number,
        [name]: string,
    }
    
    schema == {
        type: "object",
        additionalProperties: false,
        required: ["id"],
        properties: {
            id: {type: "number"},
            name: {type: "string"},
        },
    }

    Array syntax

    Here example of array where all items should be validated with one schema

    schema = [number]
    
    schema == {
      type: 'array',
      items: {type: 'number'}
    }

    Here example how we can validate items through many schemas

    schema = [number || string || {id: number}]
    
    schema == {
      type: 'array',
      items: {
        anyOf: [
          {type: 'number'},
          {type: 'string'},
          {
            type: 'object',
            additionalProperties: false,
            required: ['id'],
            properties: {
              id: {type: 'number'}
            }
          },
        ]
      }
    }

    Here index relative validation

    schema = [number, string]

    Which means that first element must be a number and second a string. Rest elements validation depends on array options like additionalItems. In this example valid will be: [1], [1, "abc"], [1, "abc", 2], []. Not valid: ["abc", 1], ["abc"]

    schema == {
      type: 'array',
      items: [
        {type: 'number'},
        {type: 'string'}
      ]
    }

    You can add any array option with it methods

    schema = [number, string].additionalItems(false)
    
    schema == {
      type: 'array',
      items: [
        {type: 'number'},
        {type: 'string'}
      ],
      additionalItems: false,
    }

    If you need one index relative element validation than you can use items method like

    firstNumber = [].items([number])
    firstString = array.items([string])
    
    firstNumber == {
      type: 'array',
      items: [{type: 'number'}]
    }
    
    firstString == {
      type: 'array',
      items: [{type: 'string'}]
    }

    This example means that at least one element in an array must be valid

    list = [...string]
    listOr = [...(string || boolean)]
    
    list == {
      type: 'array',
      contains: {type: 'string'},
    }
    listOr == {
      type: 'array',
      contains: {anyOf: [{type: 'string'}, {type: 'boolean'}]},
    }

    Combination of index relative validation and contains

    schema = [number, ...(string || boolean)]
    
    schema == {
      type: 'array',
      items: [
        {type: 'number'}
      ],
      contains: {anyOf: [{type: 'string'}, {type: 'boolean'}]},
    }

    Number patterns

    Instead of short number validator you can use one of following number patterns as value of object field.

    • int number without floating-point
    • positive positive number including 0
    • negative negative number excluding 0
    • id integer more than 0
    schema = {
        id: id,
        price: positive,
        list: [int],
    }
    
    schema == {
        type: "object",
        additionalProperties: false,
        required: ['id', 'price', 'list'],
        properties: {
            id: {
                type: "integer",
                minimum: 1,
            },
            price: {
                type: "number",
                minimum: 0,
            },
            list: {
                type: "array",
                items: {
                    type: "integer",
                }
            },
        },
    }

    String patterns

    Instead of short string validator you can use one of following string patterns as value of object field.

    • date full-date according to RFC3339.
    • time time with optional time-zone.
    • date-time date-time from the same source (time-zone is optional, in ajv it's mandatory)
    • date-time-tz date-time with time-zone required
    • uri full URI.
    • uri-reference URI reference, including full and relative URIs.
    • uri-template URI template according to RFC6570
    • email email address.
    • hostname host name according to RFC1034.
    • filename name (words with dashes) with extension
    • ipv4 IP address v4.
    • ipv6 IP address v6.
    • regex tests whether a string is a valid regular expression by passing it to RegExp constructor.
    • uuid Universally Unique Identifier according to RFC4122.

    Also, regexp will be converted to {pattern: "regexp"}

    schema = {
        id: uuid,
        email: email,
        created_at: date-time,
        phone: /^\+?\d+$/,
        days: [date],
    }
    
    schema == {
        type: "object",
        additionalProperties: false,
        required: ['id', 'email', 'created_at', 'phone', 'days'],
        properties: {
            id: {
                type: "string",
                format: "uuid",
            },
            email: {
                type: "string",
                format: "email",
            },
            created_at: {
                type: "string",
                format: "date-time",
            },
            phone: {
                type: "string",
                pattern: "^\\+?\\d+$",
            },
            days: {
                type: "array",
                items: {
                    type: "string",
                    format: "date",
                }
            },
        }
    }

    Inject external schema

    You can inject an external schema in a current schema.

    User = {
        id: number,
        name: string,
    }
    
    schema = {
        action: 'update' || 'delete',
        user: User,
    }
    
    schema == {
      type: 'object',
      additionalProperties: false,
      required: ['action', 'user'],
      properties: {
        action: {
          type: 'string',
          enum: ['update', 'delete']
        },
        user: {
          type: 'object',
          additionalProperties: false,
          required: ['id', 'name'],
          properties: {
            id: {type: 'number'},
            name: {type: 'string'},
          }
        }
      }
    }

    anyOf schema

    Instead of anyOf you can use || operator

    schema = {
        data: User || Account || {type: "object"}
    }
    
    schema == {
        type: "object",
        additionalProperties: false,
        required: ['data'],
        properties: {
            data: {
                anyOf: [
                    {/* schema of User */},
                    {/* schema of Account */},
                    {type: "object"},
                ]
            }
        }
    }

    allOf schema

    Instead of allOf you can use && operator

    schema = {
        data: User && Account && {type: "object"}
    }
    
    schema == {
        type: "object",
        additionalProperties: false,
        required: ['data'],
        properties: {
            data: {
                allOf: [
                    {/* schema of User */},
                    {/* schema of Account */},
                    {type: "object"},
                ]
            }
        }
    }

    Extend schema

    To extend you can use object spread operator

    User = {
        id: number,
        data: string,
    }
    
    UserExtra = {
        name: string,
        created_at: date,
    }
    
    schema = {
       ...User,
       ...UserExtra,
      
       age: number, // add field
       data: undefined, // remove field
       created_at: date-time, // overwrite field
    }
    
    schema == {
       type: "object",
       additionalProperties: false,
       required: ['id', 'name', 'created_at', 'age'],
       properties: {
          id: {type: "number"},
          name: {type: "string"},
          created_at: {type: "string", format: "date-time"},
          age: {type: "number"},
       }
    }

    Also, you can overwrite validator options

    schema = {
       ...User,
       type: "object",
       additionalProperties: true,
    }

    Important to add type: "object" it says to compiler that this object is pure ajv validator, not simplified version.

    schema = {
       type: "object",
       additionalProperties: true,
       properties: {
          id: {type: "number"},
          data: {type: "string"},
       }
    }

    You extend even non object validators

    phone = {
        type: "string",
        pattern: "^\\d+$"
    }
    
    schema = {
        ...phone,
      
        type: "string",
        maxLength: 20,
    }

    Switch syntax

    This syntax useful in case when you write something like {...} || {...} || {...} but if validator found error then it throws tons of messages from each of that object. To help validator to figure out which object is responsible for current data you can use next syntax

    schema = (
        (
            {action: 'create'} >>
            {
                 name: string
            }
        )
        ||
        (
            {action: 'update'} >>
            {
                id: int, 
                name: string,
            }
        )
        ||
        (
            {action: 'delete'} >>
            {
                id: int
            }
        )
    )

    It will be converted to

    schema = {
        if: {
            type: 'object',
            additionalProperties: true,
            required: ['action'],
            properties: {
                action: {const: 'create'}
            }
        },
        then: {
            type: 'object',
            additionalProperties: false,
            required: ['action', 'name'],
            properties: {
                action: {const: 'create'},
                name: {type: 'string'},
            }
        },
        else: {
            if: {
                type: 'object',
                additionalProperties: true,
                required: ['action'],
                properties: {
                    action: {const: 'update'}
                }
            },
            then: {
                type: 'object',
                additionalProperties: false,
                required: ['action', 'id', 'name'],
                properties: {
                    action: {const: 'update'},
                    id: {type: 'integer'},
                    name: {type: 'string'},
                }
            },
            else: {
                if: {
                    type: 'object',
                    additionalProperties: true,
                    required: ['action'],
                    properties: {
                        action: {const: 'delete'}
                    }
                },
                then: {
                    type: 'object',
                    additionalProperties: false,
                    required: ['action', 'id'],
                    properties: {
                        action: {const: 'delete'},
                        id: {type: 'integer'},
                    }
                },
                else: {
                    oneOf: [
                        {
                            type: 'object',
                            additionalProperties: true,
                            required: ['action'],
                            properties: {
                                action: {const: 'create'}
                            }
                        },
                        {
                            type: 'object',
                            additionalProperties: true,
                            required: ['action'],
                            properties: {
                                action: {const: 'update'}
                            }
                        },
                        {
                            type: 'object',
                            additionalProperties: true,
                            required: ['action'],
                            properties: {
                                action: {const: 'delete'}
                            }
                        },
                    ]
                }
            }
        }
    }

    Notice additionalProperties: true in each if: it means we are validating only part of object and additionalProperties: false in then: with same properties from if: which means we are validating hole object. Also we need last else: {oneOf: [...]} to throw error that none of if: is not matched.

    Pure syntax

    If you want to write pure json schema than use !! operator (all included schemas will be converted).

    schema = {
        id: !!{type: 'number'},
        data: !!{
            type: 'object',
            properties: {
                name: string.minLength(1),
            },
        },
    }
    schema = {
        type: 'object',
        additionalProperties: false,
        required: ['id', 'data'],
        properties: {
            id: {type: 'number'},
            data: {
                type: 'object',
                properties: {
                    name: {
                        type: 'string',
                        minLength: 1,
                    },
                },
            },  
        },
    }

    You can mix pure syntax inside regular syntax

    schema = {
        id: int,
    
        ...!!{
            additionalProperties: true,
            someOtherOption: true,
        },
    }
    schema = {
        type: 'object',
        additionalProperties: true,
        required: ['id'],
        properties: {
            id: {type: 'integer'}
        },
        someOtherOption: true,
    }

    Schema methods

    Another great way to extend a schema is to use it methods.

    Example schema

    User = {
        id: number,
        [name]: string,
    }

    prop

    Returns schema of property.

    Here good way to reuse schema props, even if they super simple like number

    schema = {
        id: User.prop('id')
    }
    
    schema == {
        id: number
    }

    props

    Alias: pick

    Return "object" schema of props

    schema = User.props('id', 'name')
     
    schema == {
        id: number,
        [name]: string,
    }

    merge

    Aliases: add, assign, extend

    Returns extended schema

    schema = User.merge({token: uuid})
     
    schema == {
        id: number,
        [name]: string,
        token: uuid,
    }

    remove

    Alias: omit

    Returns schema without props

    schema = User.remove('id')
     
    schema == {
        [name]: string
    }

    required

    Returns same schema, only with required props. Can take many props names.

    schema = User.required('name')
    
    schema == {
        id: number,
        name: string,
    }

    notRequired

    Alias: optional

    Make fields optional

    schema = User.notRequired('id')
    
    schema == {
        [id]: number,
        [name]: string,
    }

    set

    Set schema option like additionalProperties or minLength

    schema = User.set('additionalProperties', true)
    
    schema == {
        type: "object",
        additionalProperties: true,
        required: ['id'],
        properties: {
            id: {type: "number"},
            name: {type: "string"},
        },
    }
    
    schema = {search: string.set('minLength', 3)}
    
    schema == {
        search: {
            type: "string",
            minLength: 3,
        }
    }

    get

    Return schema option value like minLength

    schema = {
        search: string.set('minLength', User.prop('name').get('minLength'))
    }

    Schema options as methods

    All schemas options are duplicated as methods

    schema = {
        id: number.minimum(1),
        search: string.minLength(3).maxLength(20),
    }
    
    schema = User.additionalProperties(true).maxProperties(10)

    Object schema inline options

    All object options can be specified inline with properties with $ sign at the beginning of option name

    schema = {
        id: number,
        
        $additionalProperties: true,
        $maxProperties: 10,
    }
    
    schema == {
        type: 'object',
        additionalProperties: true,
        maxProperties: 10,
        required: ['id'],
        properties: {
            id: {type: 'number'}
        }
    }

    Keywords

    Install

    npm i adv-parser

    DownloadsWeekly Downloads

    4

    Version

    2.0.4

    License

    MIT

    Unpacked Size

    75.3 kB

    Total Files

    80

    Last publish

    Collaborators

    • redexp