{"id":28092485,"url":"https://github.com/bou-co/parsing","last_synced_at":"2025-10-14T01:31:56.494Z","repository":{"id":280460465,"uuid":"942064494","full_name":"bou-co/parsing","owner":"bou-co","description":"Toolkit for data manipulation, validation, type generation, and efficient query splitting. Perfect for handling complex data structures, ensuring data integrity, and generating TypeScript types dynamically.","archived":false,"fork":false,"pushed_at":"2025-09-17T10:53:30.000Z","size":754,"stargazers_count":2,"open_issues_count":8,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-19T18:43:58.100Z","etag":null,"topics":["data-manipulation","react-server-components","type-generation","typescript","validation"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bou-co.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-03T14:13:29.000Z","updated_at":"2025-06-09T12:19:47.000Z","dependencies_parsed_at":"2025-03-03T15:38:50.354Z","dependency_job_id":"e71ec283-7964-4252-895e-6f278ce1c47b","html_url":"https://github.com/bou-co/parsing","commit_stats":null,"previous_names":["bou-co/parsing"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bou-co/parsing","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bou-co%2Fparsing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bou-co%2Fparsing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bou-co%2Fparsing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bou-co%2Fparsing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bou-co","download_url":"https://codeload.github.com/bou-co/parsing/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bou-co%2Fparsing/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279008629,"owners_count":26084480,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-11T02:00:06.511Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-manipulation","react-server-components","type-generation","typescript","validation"],"created_at":"2025-05-13T13:17:46.573Z","updated_at":"2025-10-14T01:31:56.486Z","avatar_url":"https://github.com/bou-co.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bou Parsing\n\nBou 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!\n\n[NPM](https://www.npmjs.com/package/@bou-co/parsing) | [GitHub](https://github.com/bou-co/parsing)\n\n## Get started\n\n```bash\nnpm i @bou-co/parsing\n```\n\n```ts\n// parser-config.ts\nimport { initializeParser } from '@bou-co/parsing';\n\nexport const { createParser } = initializeParser();\n```\n\n[View simple usage example](#define-the-data-you-want)\n\n## Features\n\n1. [Define the data you want](#define-the-data-you-want)\n2. [Generate types](#generate-types-to-your-picked-data)\n3. [Add and modify values](#adding-additional-data-or-modifying-raw-values)\n4. [Nested data structures](#nested-data-structures)\n5. [Conditional data](#conditional-data)\n6. [Merging data](#merging-data)\n7. [Variables](#variables)\n\n### Define the data you want\n\nWhen 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.\n\n```ts\nimport { createParser } from '../path-to/parser-config';\n\nconst rawDataFromApi = {\n  _id: 'abc-123',\n  title: 'Test',\n  description: 'Lorem ipsum',\n  priority: 1,\n};\n\nconst myParser = createParser({\n  title: 'string',\n  description: 'string',\n  priority: 'number',\n});\n\nconst dataThatYouWanted = await myParser(rawDataFromApi);\n```\n\nIn the example above we pick to get `title, description and priority` but omit the `_id`.\n\n**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.\n\n### Generate types to your picked data\n\nRarely 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.\n\n```ts\nimport { ParserReturnValue } from '@bou-co/parsing';\nimport { createParser } from '../path-to/parser-config';\n\nconst myParser = createParser({\n  title: 'string',\n  description: 'string',\n  priority: 'number',\n});\n\nexport type MyParserData = ParserReturnValue\u003ctypeof myParser\u003e;\n```\n\nType `MyParserData` equals to:\n\n```ts\ninterface MyParserData {\n  title?: string;\n  description?: string;\n  priority?: number;\n}\n```\n\nPossible values that are automatically turned to types are `string, number, boolean, object, any, unknown, undefined, date, array` or `array\u003cstring etc.\u003e`.\n\n**Note:** `@bou-co/parsing` type generation by default expects that any value can also be undefined!\n\n#### Using custom types\n\nIt'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.\n\n```ts\nimport { typed } from '@bou-co/parsing';\nimport { createParser } from '../path-to/parser-config';\n\ninterface Author {\n  name?: string;\n  title?: string;\n}\n\nconst anotherParser = createParser({\n  title: 'string',\n  category: typed\u003c'blog' | 'news' | 'releases'\u003e,\n  author: typed\u003cAuthor\u003e,\n});\n\nexport type AnotherParserData = ParserReturnValue\u003ctypeof anotherParser\u003e;\n```\n\nIn this case type `AnotherParserData` equals to:\n\n```ts\ninterface AnotherParserData {\n  title?: string;\n  category?: 'blog' | 'news' | 'releases';\n  author?: Author;\n}\n```\n\n### Adding additional data or modifying raw values\n\nWith `@bou-co/parsing` it's also possible to add values that are not part of the initial data.\n\n```ts\nimport { createParser } from '../path-to/parser-config';\n\nconst rawDataFromApi = {\n  _id: 'abc-123',\n  title: 'Test',\n  description: 'Lorem ipsum',\n  priority: 1,\n};\n\nconst BLOG_POST = 'blogPost';\n\nconst myParser = createParser({\n  title: 'string',\n  description: 'string',\n\n  // 1. Static value added as is\n  postType: BLOG_POST\n\n  // 2. Function return value\n  randomNumber: () =\u003e Math.random()\n\n  // 3. Promises supported\n  asyncText: async () =\u003e {\n    const awaited = await fetch('your-api').then(res =\u003e res.text())\n    return awaited;\n  }\n\n  // 4. Custom override for priority\n  priority: (context) =\u003e {\n    const { data } = context;\n    if (!data.priority) return 1;\n    return data.priority\n  }\n\n  // 5. Variation of raw value\n  metaTitle: (context) =\u003e {\n    const { data } = context;\n    if (!data.title) return 'Untitled blog post';\n    return `${data.title} - Our blog`\n  }\n});\n\nconst dataThatYouWanted = await myParser(rawDataFromApi);\n```\n\n**Note:** When using functions to set data, you might need to manually define the type of the value that the function returns!\n\n**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!\n\n### Nested data structures\n\nParsers can handle nested objects as properties defined in projection or as additional parsers.\n\n#### Nested objects in parsers\n\n```ts\nimport { createParser } from '../path-to/parser-config';\n\nconst myParser = createParser({\n  title: 'string',\n  nestedDataObject: {\n    description: 'string',\n    priority: 'number',\n  },\n});\n```\n\n#### Nested arrays in parsers\n\nAdding `'@array': true,` defines that projection inside of current level should be defined as array.\n\n```ts\nimport { createParser } from '../path-to/parser-config';\n\nconst myParser = createParser({\n  title: 'string',\n  nestedDataArray: {\n    '@array': true,\n    description: 'string',\n    priority: 'number',\n  },\n});\n```\n\n#### Nested parsers\n\n```ts\nimport { createParser } from '../path-to/parser-config';\n\nconst innerParser = createParser({\n  description: 'string',\n  priority: 1,\n});\n\nconst myParser = createParser({\n  title: 'string',\n  nestedDataObject: innerParser,\n  nestedDataArray: innerParser.asArray,\n});\n```\n\n### Conditional data\n\nParsing supports fully conditional data picking and addition with `@if`.\n\n```ts\nimport { createParser } from '../path-to/parser-config';\n\nconst myParser = createParser({\n  title: 'string',\n  priority: 'number',\n  '@if': [\n    // 1. Show description only if priority is 1\n    {\n      when: (context) =\u003e context.data.priority === 1,\n      then: {\n        description: 'string',\n      },\n    },\n    // 2. Omit description and add \"highPriority: true\" if priority is above 1\n    {\n      when: (context) =\u003e context.data.priority \u003e 1,\n      then: {\n        highPriority: true,\n      },\n    },\n    // 3. Modify description and add \"lowPriority: true\" if priority is below 1\n    {\n      when: (context) =\u003e context.data.priority \u003c 1,\n      then: {\n        lowPriority: true,\n        description: (context) =\u003e context.data.description + '?',\n      },\n    },\n  ],\n});\n```\n\n### Merging data\n\nAdding data as individual property \u0026 value pairs is good when only few values are added but to manage larger additions you can use `@combine`.\n\n```ts\nimport { createParser } from '../path-to/parser-config';\n\nconst rawDataFromApi = {\n  _id: 'abc-123',\n  title: 'Test',\n  description: 'Lorem ipsum',\n  priority: 1,\n};\n\nconst additionalDataParser = createParser({\n  readCount: 'number',\n  likes: 'number',\n});\n\nconst myParser = createParser({\n  title: 'string',\n  priority: 'number',\n  description: 'string',\n  '@combine': (context) =\u003e {\n    const { _id } = context.data;\n    const query = `your-api?id=${_id}`;\n    const rawAdditionalData = await fetch(query).then((res) =\u003e res.json());\n    return additionalDataParser(rawAdditionalData);\n  },\n});\n\nconst mergedData = await myParser(rawDataFromApi);\n```\n\n### Variables\n\nVariables 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.\n\n#### Global variables\n\nTo use variables anywhere, define them in your parsing config.\n\n```ts\n// parser-config.ts\nimport { initializeParser } from '@bou-co/parsing';\n\nexport const { createParser } = initializeParser(() =\u003e {\n  const currentYear = new Date().getFullYear();\n  return {\n    currentYear,\n  };\n});\n```\n\nAfter definition you can use them in the raw data.\n\n```ts\nimport { createParser } from '../path-to/parser-config';\n\nconst rawDataFromApi = {\n  title: 'Hello from {{currentYear}}',\n  description: 'Is the current year really {{currentYear}}?',\n};\n\nconst myParser = createParser({\n  title: 'string',\n  description: 'string',\n});\n\nconst result = await myParser(rawDataFromApi);\n```\n\nResult in case above is:\n\n```json\n{ \"title\": \"Hello from 2025\", \"description\": \"Is the current year really 2025?\" }\n```\n\n#### Instance variables\n\n```ts\nimport { createParser } from '../path-to/parser-config';\n\nconst rawDataFromApi = {\n  title: 'Message for the whole {{entity}}',\n  description: 'Hello {{entity}}!',\n};\n\nconst myParser = createParser({\n  title: 'string',\n  description: 'string',\n});\n\nconst instanceData = {\n  entity: 'world',\n};\n\nconst result = await myParser(rawDataFromApi, instanceData);\n```\n\nResult in case above is:\n\n```json\n{ \"title\": \"Message for the whole world\", \"description\": \"Hello world!\" }\n```\n\n#### Variable fallbacks\n\nFor any variable provided in the data, there can be a fallback value in a form of another variable, string, number or boolean. Fallback values can be added with adding `||` after the initial variable and after that a secondary variable or just a value.\n\n```ts\nimport { createParser } from '../path-to/parser-config';\n\nconst rawDataFromApi = {\n  title: 'Message for the whole {{notFound || \"city\"}}',\n  description: 'Hello {{firstName || lastName || \"Doe\"}}!',\n  score: '{{score || 0}}',\n  active: '{{isActive || false}}',\n};\n\nconst myParser = createParser({\n  title: 'string',\n  description: 'string',\n  score: 'number',\n  active: 'boolean',\n});\n\nconst instanceData = {\n  lastName: 'Johnson',\n};\n\nconst result = await myParser(rawDataFromApi, instanceData);\n```\n\nResult in case above is:\n\n```json\n{\n  \"title\": \"Message for the whole city\",\n  \"description\": \"Hello Johnson!\",\n  \"score\": 0,\n  \"active\": false\n}\n```\n\n#### Variable pipes\n\nIf you need to transform the value of a variable, you can do that with pipes. Pipes are useful for example when you want to transform an ISO date to something meant for humans. Pipes are defined in code as variables that are funtions and get the current variable value in the context as data. Pipes can also take in params that are then an array in the context.\n\n```ts\nimport { createParser } from '../path-to/parser-config';\n\nconst rawDataFromApi = {\n  title: 'Hello from {{title | uppercase}}',\n  publishedAt: 'This is published at {{date | toDateString}}',\n  score: '{{score | multiply:100 }}', // Add param for how much should the value be multiplied\n};\n\nconst myParser = createParser({\n  title: 'string',\n  publishedAt: 'string',\n  score: 'number',\n});\n\nconst instanceData = {\n  // Variable values\n  title: 'the space',\n  publishedAt: '2025-05-22T12:00:00',\n  score: 0.42,\n  // Pipe functions (could most likely be added with initializeParser and not per instance)\n  uppercase: ({ data }) =\u003e data.toUpperCase(),\n  toDateString: ({ data }) =\u003e new Date(data).toLocaleString(),\n  multiply: ({ data, params: [by] = [2] }) =\u003e data * by,\n};\n\nconst result = await myParser(rawDataFromApi, instanceData);\n```\n\nResult in case above is:\n\n```json\n{\n  \"title\": \"Message for THE SPACE\",\n  \"publishedAt\": \"5/22/2025, 12:00:00 PM\",\n  \"score\": 42\n}\n```\n\n---\n\n\u003cfooter\u003e\n\nDeveloped by [Bou](https://bou.co/)\n\n\u003c/footer\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbou-co%2Fparsing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbou-co%2Fparsing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbou-co%2Fparsing/lists"}