Scrubbr
Serialize and sanitize JSON data using TypeScript.
Serializing data sent from the webserver to the client shouldn't be hard. If you're already using TypeScript, you have everything you need. Scrubbr will use your TypeScript types to deeply transform and sanitize your data.
Table of contents
- Install
- Quick Start
- Custom Serializer Functions
- Type Serializers
- Path Serializers
- Cache the generated schema to disk
- Schema Validation
- Troubleshooting
- Look at the generated schema
- Enable debug logging
Install
npm i -S scrubbr
Quick Start
- Define a TypeScript file as your master schema:
type UserList = {
users: User[];
};
type User = {
name: string;
image: string;
};
- Load it into Scrubbr and serialize your data:
import Scrubbr from 'scrubbr';
const scrubbr = new Scrubbr('./schema.ts');
async function api() {
const data = getUsers();
return await scrubbr.serialize('UserList', data);
}
function getUsers() {
return {
users: [
{
name: 'John Doe',
image: 'http://i.pravatar.cc/300',
email: 'donotspam@me.com',
password: 'xxxsecretxxx',
},
],
};
}
- Output
{
"users": [
{
"name": "John Doe",
"image": "http://i.pravatar.cc/300"
}
]
}
Custom Serializer Functions
You can define additional functions for custom serializations.
Type Serializers
Type serializer functions are called every time a matching TypeScript type is encountered.
For example, if you want to use another type to serialize a logged-in user:
import Scrubbr, { useType } from 'scrubbr';
scrubbr.addTypeSerializer('User', (data, state) => {
if (data.id === state.context.loggedInUserId) {
return useType('UserPrivileged');
}
return data;
});
const context = {
loggedInUserId: 10,
};
const serialized = await scrubbr.serialize('PostList', data, context);
Path Serializers
This serializer is called at each node of the data object, regardless of type. It's called a path serializer because you'll use the state.path
value to determine which node you're serializing.
In this example we want to convert every createdAt
date value to the local timezone.
import moment from 'moment-timezone';
import Scrubbr, { useType } from 'scrubbr';
scrubbr.addPathSerializer((data, state) => {
const path = state.path;
if (path.match(/\.createdAt$/)) {
return moment(data).tz(state.context.timezone).format();
}
return data;
});
const context = {
timezone: 'America/Los_Angeles',
};
const serialized = await scrubbr.serialize('PostList', data, context);
Try it yourself
It's easy to try it yourself with the included example in example/index.ts
. Just clone this repo, install the dependencies (npm install
) and then run the example app with:
npm run example
Caching the generated schema to disk
To optimize startup time, you can save the schema object scrubbr uses internally to disk during your build step and then load it directly when you initialize scrubbr. Internally, scrubbr uses the ts-json-schema-generator library to convert TypeScript to a JSON schema file. NOTE: you cannot load any JSON schema file into scrubbr, it needs to follow the conventions of ts-json-schema-generator.
Build
npx ts-json-schema-generator -f ./tsconfig.json -e all -p ./schema.ts -o ./dist/schema.json
Runtime code
import Scrubbr, { JSONSchemaDefinitions } from 'scrubbr';
import * as schema from './schema.json';
const scrubbr = new Scrubbr(schema as JSONSchemaDefinitions);
Schema Validation
For the sake of performance and simplicity, scrubber does not perform a schema validation step (it outputs data, not validates). However, under the hood scrubbr converts TypeScript to JSONSchema (via the great ts-json-schema-generator package). So you can easily use ajv to validate the serialized object.
import Ajv from 'ajv';
import Scrubbr from 'scrubbr';
const scrubbr = new Scrubbr('./schema.ts');
async function main() {
const output = await scrubbr.serialize('UserList', data);
const jsonSchema = scrubbr.getSchema();
const ajv = new Ajv();
const schemaValidator = ajv.compile(jsonSchema);
const isValid = schemaValidator(output);
if (!isValid) {
console.error(schemaValidator.errors);
}
}
Troubleshooting
Look at the generated schema
If scrubbr is not returning the data you're expecting, the first place to look is at the internal schema definitions:
console.log(scrubbr.getSchema());
This is a JSON schema that is created from your TypeScript file.
Next look at the schema definition for the TypeScript type you're trying to serialize to.
console.log(scrubbr.getSchemaForType('UserList'));
Verify that this returns a JSON Schema object and that it contains the properties you want serialized.
Debugging output
Enable debug logging:
import Scrubbr, { LogLevel } from 'scrubbr';
const scrubbr = new Scrubbr('./schema.ts', { logLevel: LogLevel.DEBUG });
Scrubbr can also nest the logs to make it easier to read:
const scrubbr = new Scrubbr('./schema.ts', {
logLevel: LogLevel.DEBUG,
logNesting: true,
});
And you can even enter the indent string to use for each level of nesting:
const scrubbr = new Scrubbr('./schema.ts', {
logLevel: LogLevel.DEBUG,
logNesting: '~~>',
});
License
MIT