diff --git a/README.md b/README.md index 4da548a..8866586 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ required and will default to an generic version of the parser. - `Parse.regex(regex: RegExp): (val: unknown) => string` - `Parse.literal(literal: unknown): (val: unknown) => literal` - `Parse.instanceOf(classDef: Class): (val: unknown) => T` +- `Parse.recordOf(keyParser: Parser, valParser: Parser): (val: unknown) => Record` ```ts // An array of strings @@ -117,6 +118,26 @@ const numberOrNull = Parse.nullable(Parse.number); const optionalArrayOfStrings = Parse.optional(Parse.arrayOf(Parse.string))); ``` +Both `nullable` and `optional` can be passed `true` as a second parameter +to allow parsing `null` or `undefined` into the respective empty value. + +For example, if you want to allow parsing a field that may be `null` or `undefined`, +but want to return only `null` if either is parsed, you can use: + +```typescript +const nullifiedVal = Parse.nullable(undefined, true); // parses to null +``` + +### Defaulting and Transforming + +Use `Parse.defaulted()` and `Parse.transformed()` to wrap parsers and provide +a default value or value transformer + +```typescript +const parsedWithDefault = Parse.defaulted(Parse.number, 42)(undefined); // 42 +const asNum = Parse.transformed(Parse.string, (val) => parseInt(val, 10))("42"); // 42 +``` + ### Extracting Types from Parsers When composing parsers to create more complex types, you can use the `ReturnType` diff --git a/package.json b/package.json index c2e5ad5..ad8c79a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@endeavorance/parsec", - "version": "0.7.0", + "version": "0.8.0", "author": "Endeavorance", "type": "module", "module": "dist/index.js", diff --git a/src/index.ts b/src/index.ts index 38da37a..e0c0e1c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -241,6 +241,23 @@ export const Parse = { }; }, + transformed( + parser: (val: unknown) => I, + transformer: (val: I) => O, + shapeName = "Transformed Value", + ): (val: unknown) => O { + return (val: unknown) => { + try { + return transformer(parser(val)); + } catch (error) { + if (error instanceof ParseError) { + error.path += shapeName; + } + throw error; + } + }; + }, + oneOf[]>( parsers: T, shapeName = "Union", diff --git a/src/test/parse.test.ts b/src/test/parse.test.ts index c6fa439..cd800f9 100644 --- a/src/test/parse.test.ts +++ b/src/test/parse.test.ts @@ -1,6 +1,8 @@ import { describe, expect, test } from "bun:test"; import { Parse, ParseError } from "../index.ts"; +class TestClass { } + describe(Parse.unknown, () => { test("returns the value as provided", () => { const val = Parse.unknown("hello"); @@ -282,8 +284,6 @@ describe(Parse.nan, () => { }); }); -class TestClass { } - describe(Parse.instanceOf, () => { test("Parses instances of a class", () => { const testClass = new TestClass(); @@ -339,3 +339,14 @@ describe(Parse.recordOf, () => { expect(() => stringToString(badRec)).toThrow(ParseError); }); }); + +describe(Parse.transformed, () => { + test("It transforms the parsed value", () => { + const parser = Parse.transformed(Parse.string, (val) => + Number.parseInt(val, 10), + ); + + const parsedVal = parser("123"); + expect(parsedVal).toBe(123); + }); +});