Bou Parsing is your ultimate sidekick for taming unruly data! Whether you're wrangling data from APIs, generating TypeScript types on the fly, or splitting complex queries into bite-sized pieces, Bou Parsing has got you covered. With its powerful yet easy-to-use functions, you can effortlessly manipulate, validate, and transform your data into exactly what you need. Say goodbye to tedious data handling and hello to a smoother, more efficient workflow with Bou Parsing!
npm i @bou-co/parsing
// parser-config.ts
import { initializeParser } from '@bou-co/parsing';
export const { createParser } = initializeParser();
- Define the data you want
- Generate types
- Add and modify values
- Nested data structures
- Conditional data
- Merging data
- Variables
When querying data with an API that does not support picking what you want, createParser
function can be used to pick the data you need and remove the rest.
import { createParser } from '../path-to/parser-config';
const rawDataFromApi = {
_id: 'abc-123',
title: 'Test',
description: 'Lorem ipsum',
priority: 1,
};
const myParser = createParser({
title: 'string',
description: 'string',
priority: 'number',
});
const dataThatYouWanted = await myParser(rawDataFromApi);
In the example above we pick to get title, description and priority
but omit the _id
.
Note: value returned by createParser
is an async function as parsers do have a wide support for promises. For React.js component usage we have developed a client side hook useParserValue
to allow parser usage easily inside of React.js.
Rarely you can get good and easy type generation from external APIs (especially from CMS). With ParserReturnValue
it's possible to use your parser projection as the TypeScript type instead of writing the types on your own.
import { ParserReturnValue } from '@bou-co/parsing';
import { createParser } from '../path-to/parser-config';
const myParser = createParser({
title: 'string',
description: 'string',
priority: 'number',
});
export type MyParserData = ParserReturnValue<typeof myParser>;
Type MyParserData
equals to:
interface MyParserData {
title?: string;
description?: string;
priority?: number;
}
Possible values that are automatically turned to types are string, number, boolean, object, any, unknown, undefined, date, array
or array<string etc.>
.
Note: @bou-co/parsing
type generation by default expects that any value can also be undefined!
It's also possible to use custom types for value with typed
function. With typed
you can pass any custom TypeScript values to be used as values generated with the typing.
import { typed } from '@bou-co/parsing';
import { createParser } from '../path-to/parser-config';
interface Author {
name?: string;
title?: string;
}
const anotherParser = createParser({
title: 'string',
category: typed<'blog' | 'news' | 'releases'>,
author: typed<Author>,
});
export type AnotherParserData = ParserReturnValue<typeof anotherParser>;
In this case type AnotherParserData
equals to:
interface AnotherParserData {
title?: string;
category?: 'blog' | 'news' | 'releases';
author?: Author;
}
With @bou-co/parsing
it's also possible to add values that are not part of the initial data.
import { createParser } from '../path-to/parser-config';
const rawDataFromApi = {
_id: 'abc-123',
title: 'Test',
description: 'Lorem ipsum',
priority: 1,
};
const BLOG_POST = 'blogPost';
const myParser = createParser({
title: 'string',
description: 'string',
// 1. Static value added as is
postType: BLOG_POST
// 2. Function return value
randomNumber: () => Math.random()
// 3. Promises supported
asyncText: async () => {
const awaited = await fetch('your-api').then(res => res.text())
return awaited;
}
// 4. Custom override for priority
priority: (context) => {
const { data } = context;
if (!data.priority) return 1;
return data.priority
}
// 5. Variation of raw value
metaTitle: (context) => {
const { data } = context;
if (!data.title) return 'Untitled blog post';
return `${data.title} - Our blog`
}
});
const dataThatYouWanted = await myParser(rawDataFromApi);
Note: When using functions to set data, you might need to manually define the type of the value that the function returns!
Good to know: The type of first argument (context) for any function is ParserContext
and it contains current raw data as "data" but also some information about the current parser!
Parsers can handle nested objects as properties defined in projection or as additional parsers.
import { createParser } from '../path-to/parser-config';
const myParser = createParser({
title: 'string',
nestedDataObject: {
description: 'string',
priority: 'number',
},
});
Adding '@array': true,
defines that projection inside of current level should be defined as array.
import { createParser } from '../path-to/parser-config';
const myParser = createParser({
title: 'string',
nestedDataArray: {
'@array': true,
description: 'string',
priority: 'number',
},
});
import { createParser } from '../path-to/parser-config';
const innerParser = createParser({
description: 'string',
priority: 1,
});
const myParser = createParser({
title: 'string',
nestedDataObject: innerParser,
nestedDataArray: innerParser.asArray,
});
Parsing supports fully conditional data picking and addition with @if
.
import { createParser } from '../path-to/parser-config';
const myParser = createParser({
title: 'string',
priority: 'number',
'@if': [
// 1. Show description only if priority is 1
{
when: (context) => context.data.priority === 1,
then: {
description: 'string',
},
},
// 2. Omit description and add "highPriority: true" if priority is above 1
{
when: (context) => context.data.priority > 1,
then: {
highPriority: true,
},
},
// 3. Modify description and add "lowPriority: true" if priority is below 1
{
when: (context) => context.data.priority < 1,
then: {
lowPriority: true,
description: (context) => context.data.description + '?',
},
},
],
});
Adding data as individual property & value pairs is good when only few values are added but to manage larger additions you can use @combine
.
import { createParser } from '../path-to/parser-config';
const rawDataFromApi = {
_id: 'abc-123',
title: 'Test',
description: 'Lorem ipsum',
priority: 1,
};
const additionalDataParser = createParser({
readCount: 'number',
likes: 'number',
});
const myParser = createParser({
title: 'string',
priority: 'number',
description: 'string',
'@combine': (context) => {
const { _id } = context.data;
const query = `your-api?id=${_id}`;
const rawAdditionalData = await fetch(query).then((res) => res.json());
return additionalDataParser(rawAdditionalData);
},
});
const mergedData = await myParser(rawDataFromApi);
Variables in parsing are a way to easily edit string values that are coming from raw data. Variables can be used to easily add data about the build, render or current user.
To use variables anywhere, define them in your parsing config.
// parser-config.ts
import { initializeParser } from '@bou-co/parsing';
export const { createParser } = initializeParser(() => {
const currentYear = new Date().getFullYear();
return {
currentYear,
};
});
After definition you can use them in the raw data.
import { createParser } from '../path-to/parser-config';
const rawDataFromApi = {
title: 'Hello from {{currentYear}}',
description: 'Is the current year really {{currentYear}}?',
};
const myParser = createParser({
title: 'string',
description: 'string',
});
const result = await myParser(rawDataFromApi);
Result in case above is:
{ "title": "Hello from 2025", "description": "Is the current year really 2025?" }
import { createParser } from '../path-to/parser-config';
const rawDataFromApi = {
title: 'Message for the whole {{entity}}',
description: 'Hello {{entity}}!',
};
const myParser = createParser({
title: 'string',
description: 'string',
});
const instanceData = {
entity: 'world',
};
const result = await myParser(rawDataFromApi, instanceData);
Result in case above is:
{ "title": "Message for the whole world", "description": "Hello world!" }
Developed by Bou