Jack Roper

@zcabjro/vivalidate v0.3.0 adds transformation via map and flatMap

@zcabjro/vivalidate is a validation library written in typescript that focuses on being small and easy to use. Using simple primitives like string and object, you can build complex schemas and then validate incoming data at runtime while getting type declarations for free in the process.

Validation libraries often expose methods for transforming values as part of the validation process, reducing the amount of code the consumer has to write for what would otherwise have to be a two-stage process. Because vivalidate is backed by the Either type, transformation is trivial to support using map and flatMap.

map

Using map, we can validate an unknown value and then transform it from the validated type A to a new type B by passing an appropriate function. As a simple example, suppose we are validating an article like this:

const tags = v.array(v.string());
const article = v.object({ tags });
// {
//   tags: string[];
// }

But we only care about the number of tags:

const tags = v.array(v.string());
const count = tags.map(ts => ts.length);
const article = v.object({ tags: count });
// {
//   tags: number;
// }

OK that's pretty simple, but remember that we can do whatever we want inside the function:

const tags = v.array(v.string());
const featured = tags.map(ts => {
  return ts.includes('featured');
});
const article = v.object({
  tags: featured,
});
// {
//   tags: boolean;
// }

flatMap

We step things up a notch with flatMap which is more powerful than map in that the function you pass it is expected to return an Either. Why is this powerful? Because it lets you apply transformations that can fail.

Suppose we have this schema:

const date = v.string();
const article = v.object({ date });
// {
//   date: string;
// }

And suppose we'd rather date be of type Date after validation. You could do this manually after validation by parsing the validated string value. You could also attempt to use map:

const date = v.string().map(s => new Date(s));
const article = v.object({ date });
// {
//   date: Date;
// }

But if you want confidence that the date is valid, then you can use flatMap:

import {
  Either,
  right,
} from '@zcabjro/either';

function toDate(
  s: string
): v.Validated<Date> {
  const date = new Date(s);
  return isNaN(date.getTime())
    ? v.invalid('invalid date')
    : right(date);
}

const date = v.string().flatMap(toDate);
const article = v.object({ date });
// {
//   date: Date;
// }