Skip to content

Instantly share code, notes, and snippets.

@gabro
Last active February 24, 2020 16:38
Show Gist options
  • Save gabro/bb83ed574690645053b815da2082b937 to your computer and use it in GitHub Desktop.
Save gabro/bb83ed574690645053b815da2082b937 to your computer and use it in GitHub Desktop.
Dynamic object validation using $ObjMap in Flow
/* @flow */
// A simplified representation of types using phantom types (so that we store the Type information both at value and type level)
class Type<T> {};
class StringT extends Type<string> {}
class NumberT extends Type<number> {}
// A schema for a User
const User = {
name: new StringT(),
age: new NumberT()
};
// function that checks whether a value conforms to a type representation
// this is a trivial implementation that uses instance checking
function isValid<V>(value: V, type: Type<any>): boolean {
if (type instanceof StringT) {
return typeof value === 'string';
} else if (type instanceof NumberT) {
return typeof value === 'number';
}
return false;
}
// a function that validates a JSON string against a schema
// the result type is either an Error or a transformation over the schema, where
// the values have been replaced with the actual values of the object.
// e.g. { age: 'number' } is mapped to { age: 42 }
function validate<O: { [_: string]: Type<any> }>(schema: O, value: string): Error | $ObjMap<O, <V>(_: Type<V>) => V> {
try {
const parsed = JSON.parse(value);
// for each key of the parsed object, check whether the type matches the expectations
Object.keys(parsed).forEach(k => {
const value = parsed[k];
const expected = schema[k];
if (!isValid(value, expected)) {
throw TypeError(`InvalidSchema. Expected ${k} of type ${expected}, got ${value} of type ${typeof value}`);
}
});
// return the validated object
return parsed;
} catch (e) {
// reify the exception, returning it instead of throwing
return e;
}
}
const ok = validate(User, '{ "name": "gabro", "age": 42 }');
const wrongSyntax = validate(User, '{ name": "gabro", "age": 42 }');
const wrongType = validate(User, '{ "name": "gabro", "age": true }');
printOrError(ok); // 42
printOrError(wrongSyntax); // Invalid Syntax ...
printOrError(wrongType); // Invalid Schema ...
function printOrError(result) {
// Note that the instance check is crucial. The return type can be a validated object or an Error, so something like
// console.log(result.age)
// would result in type error, since we don't know yet whether it's one or the other.
if (result instanceof Error) {
const error = result;
console.log(error.message);
} else {
const { age, name } = result;
console.log(`${name} is ${age} years old`);
// also note that 'name' has type string and 'age' has type 'number'
// This won't typecheck
// console.log(age.trim())
}
}
@gabro
Copy link
Author

gabro commented Oct 2, 2016

Thanks @gcanti for tips and tricks on encoding Type

@gabro
Copy link
Author

gabro commented Oct 2, 2016

Typechecks with flow v0.33+.

Run with flow-node to get the output.

@cqfd
Copy link

cqfd commented Jan 23, 2017

This was a really helpful read–I've learned a lot from reading your and @gcanti's writing. Cheers!

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